diff --git a/CMake/coverage.cmake b/CMake/coverage.cmake index 52285ca6..ec00b504 100644 --- a/CMake/coverage.cmake +++ b/CMake/coverage.cmake @@ -40,7 +40,7 @@ function(add_converage module) COMMAND ${LLVM_COV_PATH}/llvm-cov report $ -instr-profile=${module}.profdata - -ignore-filename-regex=".*_makefiles|.*unittests" + -ignore-filename-regex=".*_makefiles|.*unittests|.*_deps" -show-region-summary=false DEPENDS ${module}-ccov-preprocessing) @@ -51,7 +51,7 @@ function(add_converage module) ${LLVM_COV_PATH}/llvm-cov show $ -instr-profile=${module}.profdata -show-line-counts-or-regions -output-dir=${module}-llvm-cov -format="html" - -ignore-filename-regex=".*_makefiles|.*unittests" > /dev/null 2>&1 + -ignore-filename-regex=".*_makefiles|.*unittests|.*_deps" > /dev/null 2>&1 DEPENDS ${module}-ccov-preprocessing) add_custom_command( diff --git a/CMakeLists.txt b/CMakeLists.txt index 28071cab..2a707110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,69 +65,36 @@ target_sources(clio PRIVATE src/rpc/RPCHelpers.cpp src/rpc/Counters.cpp src/rpc/WorkQueue.cpp - ## NextGen RPC src/rpc/common/Specs.cpp src/rpc/common/Validators.cpp - ## NextGen RPC handler - src/rpc/ngHandlers/AccountChannels.cpp - src/rpc/ngHandlers/AccountCurrencies.cpp - src/rpc/ngHandlers/AccountLines.cpp - src/rpc/ngHandlers/AccountTx.cpp - src/rpc/ngHandlers/AccountOffers.cpp - src/rpc/ngHandlers/AccountInfo.cpp - src/rpc/ngHandlers/BookOffers.cpp - src/rpc/ngHandlers/GatewayBalances.cpp - src/rpc/ngHandlers/LedgerEntry.cpp - src/rpc/ngHandlers/LedgerRange.cpp - src/rpc/ngHandlers/TransactionEntry.cpp - src/rpc/ngHandlers/Tx.cpp - src/rpc/ngHandlers/Random.cpp - src/rpc/ngHandlers/NoRippleCheck.cpp - src/rpc/ngHandlers/NFTInfo.cpp - src/rpc/ngHandlers/NFTOffersCommon.cpp - src/rpc/ngHandlers/NFTBuyOffers.cpp - src/rpc/ngHandlers/NFTSellOffers.cpp - src/rpc/ngHandlers/NFTHistory.cpp - src/rpc/ngHandlers/LedgerData.cpp - src/rpc/ngHandlers/AccountNFTs.cpp - src/rpc/ngHandlers/AccountObjects.cpp - src/rpc/ngHandlers/BookChanges.cpp - src/rpc/ngHandlers/Ledger.cpp - ## RPC Methods - # Account + # RPC impl + src/rpc/common/impl/HandlerProvider.cpp + ## RPC handler src/rpc/handlers/AccountChannels.cpp src/rpc/handlers/AccountCurrencies.cpp src/rpc/handlers/AccountInfo.cpp src/rpc/handlers/AccountLines.cpp - src/rpc/handlers/AccountOffers.cpp + src/rpc/handlers/AccountNFTs.cpp src/rpc/handlers/AccountObjects.cpp + src/rpc/handlers/AccountOffers.cpp + src/rpc/handlers/AccountTx.cpp + src/rpc/handlers/BookChanges.cpp + src/rpc/handlers/BookOffers.cpp src/rpc/handlers/GatewayBalances.cpp - src/rpc/handlers/NoRippleCheck.cpp - # NFT - src/rpc/handlers/NFTHistory.cpp - src/rpc/handlers/NFTInfo.cpp - src/rpc/handlers/NFTOffers.cpp - # Ledger src/rpc/handlers/Ledger.cpp src/rpc/handlers/LedgerData.cpp src/rpc/handlers/LedgerEntry.cpp src/rpc/handlers/LedgerRange.cpp - # Transaction - src/rpc/handlers/Tx.cpp - src/rpc/handlers/TransactionEntry.cpp - src/rpc/handlers/AccountTx.cpp - # Dex - src/rpc/handlers/BookChanges.cpp - src/rpc/handlers/BookOffers.cpp - # Payment Channel - src/rpc/handlers/ChannelAuthorize.cpp - src/rpc/handlers/ChannelVerify.cpp - # Subscribe - src/rpc/handlers/Subscribe.cpp - # Server - src/rpc/handlers/ServerInfo.cpp - # Utilities + src/rpc/handlers/NFTBuyOffers.cpp + src/rpc/handlers/NFTHistory.cpp + src/rpc/handlers/NFTInfo.cpp + src/rpc/handlers/NFTOffersCommon.cpp + src/rpc/handlers/NFTSellOffers.cpp + src/rpc/handlers/NoRippleCheck.cpp src/rpc/handlers/Random.cpp + src/rpc/handlers/TransactionEntry.cpp + src/rpc/handlers/Tx.cpp + ## Util src/config/Config.cpp src/log/Logger.cpp src/util/Taggable.cpp) diff --git a/src/main/main.cpp b/src/main/main.cpp index 65d69fca..b49dbb7c 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2022-2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include @@ -129,11 +131,8 @@ parseCerts(Config const& config) std::string key = contents.str(); ssl::context ctx{ssl::context::tlsv12}; - ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2); - ctx.use_certificate_chain(boost::asio::buffer(cert.data(), cert.size())); - ctx.use_private_key(boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem); return ctx; @@ -211,8 +210,16 @@ try // mode, ETL only publishes auto etl = ReportingETL::make_ReportingETL(config, ioc, backend, subscriptions, balancer, ledgers); + auto workQueue = WorkQueue::make_WorkQueue(config); + auto counters = RPC::Counters::make_Counters(workQueue); + auto const handlerProvider = + std::make_shared(backend, subscriptions, balancer, etl, counters); + auto const rpcEngine = RPC::RPCEngine::make_RPCEngine( + config, backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider); + // The server handles incoming RPCs - auto httpServer = Server::make_HttpServer(config, ioc, ctxRef, backend, subscriptions, balancer, etl, dosGuard); + auto httpServer = + Server::make_HttpServer(config, ioc, ctxRef, backend, rpcEngine, subscriptions, balancer, etl, dosGuard); // Blocks until stopped. // When stopped, shared_ptrs fall out of scope diff --git a/src/rpc/BookChangesHelper.h b/src/rpc/BookChangesHelper.h index 7b62d6fb..6ac44a8a 100644 --- a/src/rpc/BookChangesHelper.h +++ b/src/rpc/BookChangesHelper.h @@ -23,7 +23,7 @@ #include -namespace RPCng { +namespace RPC { /** * @brief Represents an entry in the book_changes' changes array. @@ -229,4 +229,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChange const }; } -} // namespace RPCng +[[nodiscard]] boost::json::object const +computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector const& transactions); + +} // namespace RPC diff --git a/src/rpc/Counters.cpp b/src/rpc/Counters.cpp index 02f5d55d..c2550163 100644 --- a/src/rpc/Counters.cpp +++ b/src/rpc/Counters.cpp @@ -40,13 +40,11 @@ Counters::initializeCounter(std::string const& method) void Counters::rpcErrored(std::string const& method) { - if (!validHandler(method)) - return; - initializeCounter(method); std::shared_lock lk(mutex_); MethodInfo& counters = methodInfo_[method]; + counters.started++; counters.errored++; } @@ -54,13 +52,11 @@ Counters::rpcErrored(std::string const& method) void Counters::rpcComplete(std::string const& method, std::chrono::microseconds const& rpcDuration) { - if (!validHandler(method)) - return; - initializeCounter(method); std::shared_lock lk(mutex_); MethodInfo& counters = methodInfo_[method]; + counters.started++; counters.finished++; counters.duration += rpcDuration.count(); @@ -69,13 +65,11 @@ Counters::rpcComplete(std::string const& method, std::chrono::microseconds const void Counters::rpcForwarded(std::string const& method) { - if (!validHandler(method)) - return; - initializeCounter(method); std::shared_lock lk(mutex_); MethodInfo& counters = methodInfo_[method]; + counters.forwarded++; } @@ -83,13 +77,14 @@ boost::json::object Counters::report() const { std::shared_lock lk(mutex_); - boost::json::object obj = {}; + auto obj = boost::json::object{}; + obj[JS(rpc)] = boost::json::object{}; auto& rpc = obj[JS(rpc)].as_object(); for (auto const& [method, info] : methodInfo_) { - boost::json::object counters = {}; + auto counters = boost::json::object{}; counters[JS(started)] = std::to_string(info.started); counters[JS(finished)] = std::to_string(info.finished); counters[JS(errored)] = std::to_string(info.errored); @@ -98,6 +93,7 @@ Counters::report() const rpc[method] = std::move(counters); } + obj["work_queue"] = workQueue_.get().report(); return obj; diff --git a/src/rpc/Counters.h b/src/rpc/Counters.h index 5d225c7b..e14c9e83 100644 --- a/src/rpc/Counters.h +++ b/src/rpc/Counters.h @@ -55,6 +55,12 @@ private: public: Counters(WorkQueue const& wq) : workQueue_(std::cref(wq)){}; + static Counters + make_Counters(WorkQueue const& wq) + { + return Counters{wq}; + } + void rpcErrored(std::string const& method); diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index 409881d1..f487dc24 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -47,7 +47,8 @@ getWarningInfo(WarningCode code) "want to talk to rippled, include 'ledger_index':'current' in your " "request"}, {warnRPC_OUTDATED, "This server may be out of date"}, - {warnRPC_RATE_LIMIT, "You are about to be rate limited"}}; + {warnRPC_RATE_LIMIT, "You are about to be rate limited"}, + }; auto matchByCode = [code](auto const& info) { return info.code == code; }; if (auto it = find_if(begin(infos), end(infos), matchByCode); it != end(infos)) @@ -59,10 +60,12 @@ getWarningInfo(WarningCode code) boost::json::object makeWarning(WarningCode code) { - boost::json::object json; + auto json = boost::json::object{}; auto const& info = getWarningInfo(code); + json["id"] = code; json["message"] = static_cast(info.message); + return json; } @@ -94,6 +97,7 @@ makeError(RippledError err, optional customError, optional customError, optional(&code)) return *err != RippledError::rpcSUCCESS; + return true; } @@ -113,6 +113,7 @@ struct Status { if (auto err = std::get_if(&code)) return *err == other; + return false; } @@ -127,6 +128,7 @@ struct Status { if (auto err = std::get_if(&code)) return *err == other; + return false; } }; @@ -180,6 +182,7 @@ public: explicit AccountNotFoundError(std::string const& acct) : account(acct) { } + const char* what() const throw() override { diff --git a/src/rpc/ngHandlers/LedgerRange.cpp b/src/rpc/HandlerTable.h similarity index 61% rename from src/rpc/ngHandlers/LedgerRange.cpp rename to src/rpc/HandlerTable.h index 781c60ce..aeb886fd 100644 --- a/src/rpc/ngHandlers/LedgerRange.cpp +++ b/src/rpc/HandlerTable.h @@ -17,33 +17,43 @@ */ //============================================================================== -#include -#include +#pragma once +#include +#include + +#include #include +#include -namespace RPCng { +namespace RPC { -LedgerRangeHandler::Result -LedgerRangeHandler::process() const +class HandlerTable { - if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange) - { - return Output{*maybeRange}; - } - else - { - return Error{RPC::Status{RPC::RippledError::rpcNOT_READY, "rangeNotFound"}}; - } -} + std::shared_ptr provider_; -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerRangeHandler::Output const& output) -{ - jv = boost::json::object{ - {JS(ledger_index_min), output.range.minSequence}, - {JS(ledger_index_max), output.range.maxSequence}, - }; -} +public: + HandlerTable(std::shared_ptr const& provider) : provider_{provider} + { + } -} // namespace RPCng + bool + contains(std::string const& method) const + { + return provider_->contains(method); + } + + std::optional + getHandler(std::string const& command) const + { + return provider_->getHandler(command); + } + + bool + isClioOnly(std::string const& command) const + { + return provider_->isClioOnly(command); + } +}; + +} // namespace RPC diff --git a/src/rpc/Handlers.h b/src/rpc/Handlers.h deleted file mode 100644 index f2b36d86..00000000 --- a/src/rpc/Handlers.h +++ /dev/null @@ -1,122 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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. -*/ -//============================================================================== - -#pragma once - -#include - -namespace RPC { -/* - * This file just contains declarations for all of the handlers - */ - -// account state methods -Result -doAccountInfo(Context const& context); - -Result -doAccountChannels(Context const& context); - -Result -doAccountCurrencies(Context const& context); - -Result -doAccountLines(Context const& context); - -Result -doAccountNFTs(Context const& context); - -Result -doAccountObjects(Context const& context); - -Result -doAccountOffers(Context const& context); - -Result -doGatewayBalances(Context const& context); - -Result -doNoRippleCheck(Context const& context); - -// channels methods - -Result -doChannelAuthorize(Context const& context); - -Result -doChannelVerify(Context const& context); - -// book methods -[[nodiscard]] Result -doBookChanges(Context const& context); - -Result -doBookOffers(Context const& context); - -// NFT methods -Result -doNFTBuyOffers(Context const& context); - -Result -doNFTSellOffers(Context const& context); - -Result -doNFTInfo(Context const& context); - -Result -doNFTHistory(Context const& context); - -// ledger methods -Result -doLedger(Context const& context); - -Result -doLedgerEntry(Context const& context); - -Result -doLedgerData(Context const& context); - -Result -doLedgerRange(Context const& context); - -// transaction methods -Result -doTx(Context const& context); - -Result -doTransactionEntry(Context const& context); - -Result -doAccountTx(Context const& context); - -// subscriptions -Result -doSubscribe(Context const& context); - -Result -doUnsubscribe(Context const& context); - -// server methods -Result -doServerInfo(Context const& context); - -// Utility methods -Result -doRandom(Context const& context); -} // namespace RPC diff --git a/src/rpc/ngHandlers/Random.cpp b/src/rpc/JS.h similarity index 65% rename from src/rpc/ngHandlers/Random.cpp rename to src/rpc/JS.h index 73d6b176..b9df8486 100644 --- a/src/rpc/ngHandlers/Random.cpp +++ b/src/rpc/JS.h @@ -17,29 +17,13 @@ */ //============================================================================== -#include -#include +#pragma once -#include -#include +#include -namespace RPCng { +// Useful macro for borrowing from ripple::jss +// static strings. (J)son (S)trings +#define JS(x) ripple::jss::x.c_str() -RandomHandler::Result -RandomHandler::process() const -{ - ripple::uint256 rand; - beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng()); - - return Output{ripple::strHex(rand)}; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, RandomHandler::Output const& output) -{ - jv = { - {JS(random), output.random}, - }; -} - -} // namespace RPCng +// Access (SF)ield name (S)trings +#define SFS(x) ripple::x.jsonName.c_str() diff --git a/src/rpc/RPC.cpp b/src/rpc/RPC.cpp index 0e339a7e..5a54c71e 100644 --- a/src/rpc/RPC.cpp +++ b/src/rpc/RPC.cpp @@ -19,8 +19,8 @@ #include #include -#include #include +#include #include #include @@ -30,6 +30,7 @@ using namespace std; using namespace clio; +using namespace RPC; // local to compilation unit loggers namespace { @@ -38,49 +39,14 @@ clio::Logger gLog{"RPC"}; } // namespace namespace RPC { -Context::Context( - boost::asio::yield_context& yield_, - string const& command_, - uint32_t version_, - boost::json::object const& params_, - shared_ptr const& backend_, - shared_ptr const& subscriptions_, - shared_ptr const& balancer_, - shared_ptr const& etl_, - shared_ptr const& session_, - util::TagDecoratorFactory const& tagFactory_, - Backend::LedgerRange const& range_, - Counters& counters_, - string const& clientIp_) - : Taggable(tagFactory_) - , yield(yield_) - , method(command_) - , version(version_) - , params(params_) - , backend(backend_) - , subscriptions(subscriptions_) - , balancer(balancer_) - , etl(etl_) - , session(session_) - , range(range_) - , counters(counters_) - , clientIp(clientIp_) -{ - gPerfLog.debug() << tag() << "new Context created"; -} -optional +optional make_WsContext( boost::asio::yield_context& yc, boost::json::object const& request, - shared_ptr const& backend, - shared_ptr const& subscriptions, - shared_ptr const& balancer, - shared_ptr const& etl, shared_ptr const& session, util::TagDecoratorFactory const& tagFactory, Backend::LedgerRange const& range, - Counters& counters, string const& clientIp) { boost::json::value commandValue = nullptr; @@ -93,22 +59,15 @@ make_WsContext( return {}; string command = commandValue.as_string().c_str(); - - return make_optional( - yc, command, 1, request, backend, subscriptions, balancer, etl, session, tagFactory, range, counters, clientIp); + return make_optional(yc, command, 1, request, session, tagFactory, range, clientIp); } -optional +optional make_HttpContext( boost::asio::yield_context& yc, boost::json::object const& request, - shared_ptr const& backend, - shared_ptr const& subscriptions, - shared_ptr const& balancer, - shared_ptr const& etl, util::TagDecoratorFactory const& tagFactory, Backend::LedgerRange const& range, - RPC::Counters& counters, string const& clientIp) { if (!request.contains("method") || !request.at("method").is_string()) @@ -130,105 +89,9 @@ make_HttpContext( if (!array.at(0).is_object()) return {}; - return make_optional( - yc, - command, - 1, - array.at(0).as_object(), - backend, - subscriptions, - balancer, - etl, - nullptr, - tagFactory, - range, - counters, - clientIp); + return make_optional(yc, command, 1, array.at(0).as_object(), nullptr, tagFactory, range, clientIp); } -using LimitRange = tuple; -using HandlerFunction = function; - -struct Handler -{ - string method; - function handler; - optional limit; - bool isClioOnly = false; -}; - -class HandlerTable -{ - unordered_map handlerMap_; - -public: - HandlerTable(initializer_list handlers) - { - for (auto const& handler : handlers) - { - handlerMap_[handler.method] = std::move(handler); - } - } - - bool - contains(string const& method) - { - return handlerMap_.contains(method); - } - - optional - getLimitRange(string const& command) - { - if (!handlerMap_.contains(command)) - return {}; - - return handlerMap_[command].limit; - } - - optional - getHandler(string const& command) - { - if (!handlerMap_.contains(command)) - return {}; - - return handlerMap_[command].handler; - } - - bool - isClioOnly(string const& command) - { - return handlerMap_.contains(command) && handlerMap_[command].isClioOnly; - } -}; - -static HandlerTable handlerTable{ - {"account_channels", &doAccountChannels, LimitRange{10, 50, 256}}, - {"account_currencies", &doAccountCurrencies, {}}, - {"account_info", &doAccountInfo, {}}, - {"account_lines", &doAccountLines, LimitRange{10, 50, 256}}, - {"account_nfts", &doAccountNFTs, LimitRange{1, 5, 10}}, - {"account_objects", &doAccountObjects, LimitRange{10, 50, 256}}, - {"account_offers", &doAccountOffers, LimitRange{10, 50, 256}}, - {"account_tx", &doAccountTx, LimitRange{1, 50, 100}}, - {"gateway_balances", &doGatewayBalances, {}}, - {"noripple_check", &doNoRippleCheck, LimitRange{1, 300, 500}}, - {"book_changes", &doBookChanges, {}}, - {"book_offers", &doBookOffers, LimitRange{1, 50, 100}}, - {"ledger", &doLedger, {}}, - {"ledger_data", &doLedgerData, LimitRange{1, 100, 2048}}, - {"nft_buy_offers", &doNFTBuyOffers, LimitRange{1, 50, 100}}, - {"nft_history", &doNFTHistory, LimitRange{1, 50, 100}, true}, - {"nft_info", &doNFTInfo, {}, true}, - {"nft_sell_offers", &doNFTSellOffers, LimitRange{1, 50, 100}}, - {"ledger_entry", &doLedgerEntry, {}}, - {"ledger_range", &doLedgerRange, {}}, - {"subscribe", &doSubscribe, {}}, - {"server_info", &doServerInfo, {}}, - {"unsubscribe", &doUnsubscribe, {}}, - {"tx", &doTx, {}}, - {"transaction_entry", &doTransactionEntry, {}}, - {"random", &doRandom, {}}}; - static unordered_set forwardCommands{ "submit", "submit_multisigned", @@ -240,57 +103,55 @@ static unordered_set forwardCommands{ "channel_authorize", "channel_verify"}; -bool -validHandler(string const& method) +RPCEngine::RPCEngine( + std::shared_ptr const& backend, + std::shared_ptr const& subscriptions, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + clio::DOSGuard const& dosGuard, + WorkQueue& workQueue, + Counters& counters, + std::shared_ptr const& handlerProvider) + : backend_{backend} + , subscriptions_{subscriptions} + , balancer_{balancer} + , dosGuard_{std::cref(dosGuard)} + , workQueue_{std::ref(workQueue)} + , counters_{std::ref(counters)} + , handlerTable_{handlerProvider} { - return handlerTable.contains(method) || forwardCommands.contains(method); +} + +std::shared_ptr +RPCEngine::make_RPCEngine( + clio::Config const& config, + std::shared_ptr const& backend, + std::shared_ptr const& subscriptions, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + clio::DOSGuard const& dosGuard, + WorkQueue& workQueue, + Counters& counters, + std::shared_ptr const& handlerProvider) +{ + return std::make_shared( + backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider); } bool -isClioOnly(string const& method) +RPCEngine::validHandler(string const& method) const { - return handlerTable.isClioOnly(method); + return handlerTable_.contains(method) || forwardCommands.contains(method); } bool -shouldSuppressValidatedFlag(RPC::Context const& context) +RPCEngine::isClioOnly(string const& method) const { - return boost::iequals(context.method, "subscribe") || boost::iequals(context.method, "unsubscribe"); -} - -Status -getLimit(RPC::Context const& context, uint32_t& limit) -{ - if (!handlerTable.getHandler(context.method)) - return Status{RippledError::rpcUNKNOWN_COMMAND}; - - if (!handlerTable.getLimitRange(context.method)) - return Status{RippledError::rpcINVALID_PARAMS, "rpcDoesNotRequireLimit"}; - - auto [lo, def, hi] = *handlerTable.getLimitRange(context.method); - - if (context.params.contains(JS(limit))) - { - string errMsg = "Invalid field 'limit', not unsigned integer."; - if (!context.params.at(JS(limit)).is_int64()) - return Status{RippledError::rpcINVALID_PARAMS, errMsg}; - - int input = context.params.at(JS(limit)).as_int64(); - if (input <= 0) - return Status{RippledError::rpcINVALID_PARAMS, errMsg}; - - limit = clamp(static_cast(input), lo, hi); - } - else - { - limit = def; - } - - return {}; + return handlerTable_.isClioOnly(method); } bool -shouldForwardToRippled(Context const& ctx) +RPCEngine::shouldForwardToRippled(Web::Context const& ctx) const { auto request = ctx.params; @@ -310,16 +171,15 @@ shouldForwardToRippled(Context const& ctx) } Result -buildResponse(Context const& ctx) +RPCEngine::buildResponse(Web::Context const& ctx) { if (shouldForwardToRippled(ctx)) { - boost::json::object toForward = ctx.params; + auto toForward = ctx.params; toForward["command"] = ctx.method; - auto res = ctx.balancer->forwardToRippled(toForward, ctx.clientIp, ctx.yield); - - ctx.counters.rpcForwarded(ctx.method); + auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield); + notifyForwarded(ctx.method); if (!res) return Status{RippledError::rpcFAILED_TO_FORWARD}; @@ -327,32 +187,30 @@ buildResponse(Context const& ctx) return *res; } - if (ctx.method == "ping") - return boost::json::object{}; - - if (ctx.backend->isTooBusy()) + if (backend_->isTooBusy()) { gLog.error() << "Database is too busy. Rejecting request"; return Status{RippledError::rpcTOO_BUSY}; } - auto method = handlerTable.getHandler(ctx.method); - + auto const method = handlerTable_.getHandler(ctx.method); if (!method) return Status{RippledError::rpcUNKNOWN_COMMAND}; try { gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method << '`'; - auto v = (*method)(ctx); + + auto const isAdmin = ctx.clientIp == "127.0.0.1"; // TODO: this should be a strategy + auto const context = Context{ctx.yield, ctx.session, isAdmin, ctx.clientIp}; + auto const v = (*method).process(ctx.params, context); + gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method << '`'; - if (auto object = get_if(&v); object && not shouldSuppressValidatedFlag(ctx)) - { - (*object)[JS(validated)] = true; - } - - return v; + if (v) + return v->as_object(); + else + return Status{v.error()}; } catch (InvalidParamsError const& err) { @@ -374,4 +232,25 @@ buildResponse(Context const& ctx) } } +void +RPCEngine::notifyComplete(std::string const& method, std::chrono::microseconds const& duration) +{ + if (validHandler(method)) + counters_.get().rpcComplete(method, duration); +} + +void +RPCEngine::notifyErrored(std::string const& method) +{ + if (validHandler(method)) + counters_.get().rpcErrored(method); +} + +void +RPCEngine::notifyForwarded(std::string const& method) +{ + if (validHandler(method)) + counters_.get().rpcForwarded(method); +} + } // namespace RPC diff --git a/src/rpc/RPC.h b/src/rpc/RPC.h index 17567b1f..13041ec2 100644 --- a/src/rpc/RPC.h +++ b/src/rpc/RPC.h @@ -20,16 +20,23 @@ #pragma once #include +#include #include #include #include +#include +#include #include +#include +#include #include #include +#include #include #include +#include #include /* @@ -50,41 +57,6 @@ class ReportingETL; namespace RPC { -struct Context : public util::Taggable -{ - clio::Logger perfLog_{"Performance"}; - boost::asio::yield_context& yield; - std::string method; - std::uint32_t version; - boost::json::object const& params; - std::shared_ptr const& backend; - // this needs to be an actual shared_ptr, not a reference. The above - // references refer to shared_ptr members of WsBase, but WsBase contains - // SubscriptionManager as a weak_ptr, to prevent a shared_ptr cycle. - std::shared_ptr subscriptions; - std::shared_ptr const& balancer; - std::shared_ptr const& etl; - std::shared_ptr session; - Backend::LedgerRange const& range; - Counters& counters; - std::string clientIp; - - Context( - boost::asio::yield_context& yield_, - std::string const& command_, - std::uint32_t version_, - boost::json::object const& params_, - std::shared_ptr const& backend_, - std::shared_ptr const& subscriptions_, - std::shared_ptr const& balancer_, - std::shared_ptr const& etl_, - std::shared_ptr const& session_, - util::TagDecoratorFactory const& tagFactory_, - Backend::LedgerRange const& range_, - Counters& counters_, - std::string const& clientIp_); -}; - struct AccountCursor { ripple::uint256 index; @@ -105,61 +77,131 @@ struct AccountCursor using Result = std::variant; -std::optional +std::optional make_WsContext( boost::asio::yield_context& yc, boost::json::object const& request, - std::shared_ptr const& backend, - std::shared_ptr const& subscriptions, - std::shared_ptr const& balancer, - std::shared_ptr const& etl, std::shared_ptr const& session, util::TagDecoratorFactory const& tagFactory, Backend::LedgerRange const& range, - Counters& counters, std::string const& clientIp); -std::optional +std::optional make_HttpContext( boost::asio::yield_context& yc, boost::json::object const& request, - std::shared_ptr const& backend, - std::shared_ptr const& subscriptions, - std::shared_ptr const& balancer, - std::shared_ptr const& etl, util::TagDecoratorFactory const& tagFactory, Backend::LedgerRange const& range, - Counters& counters, std::string const& clientIp); -Result -buildResponse(Context const& ctx); +/** + * @brief The RPC engine that ties all RPC-related functionality together + */ +class RPCEngine +{ + std::shared_ptr backend_; + std::shared_ptr subscriptions_; + std::shared_ptr balancer_; + std::reference_wrapper dosGuard_; + std::reference_wrapper workQueue_; + std::reference_wrapper counters_; -bool -validHandler(std::string const& method); + HandlerTable handlerTable_; -bool -isClioOnly(std::string const& method); +public: + RPCEngine( + std::shared_ptr const& backend, + std::shared_ptr const& subscriptions, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + clio::DOSGuard const& dosGuard, + WorkQueue& workQueue, + Counters& counters, + std::shared_ptr const& handlerProvider); -Status -getLimit(RPC::Context const& context, std::uint32_t& limit); + static std::shared_ptr + make_RPCEngine( + clio::Config const& config, + std::shared_ptr const& backend, + std::shared_ptr const& subscriptions, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + clio::DOSGuard const& dosGuard, + WorkQueue& workQueue, + Counters& counters, + std::shared_ptr const& handlerProvider); + + /** + * @brief Main request processor routine + * @param ctx The @ref Context of the request + */ + Result + buildResponse(Web::Context const& ctx); + + /** + * @brief Used to schedule request processing onto the work queue + * @param func The lambda to execute when this request is handled + * @param ip The ip address for which this request is being executed + */ + template + bool + post(Fn&& func, std::string const& ip) + { + return workQueue_.get().postCoro(std::forward(func), dosGuard_.get().isWhiteListed(ip)); + } + + /** + * @brief Notify the system that specified method was executed + * @param method + * @param duration The time it took to execute the method specified in + * microseconds + */ + void + notifyComplete(std::string const& method, std::chrono::microseconds const& duration); + + /** + * @brief Notify the system that specified method failed to execute + * @param method + */ + void + notifyErrored(std::string const& method); + + /** + * @brief Notify the system that specified method execution was forwarded to rippled + * @param method + */ + void + notifyForwarded(std::string const& method); + +private: + bool + shouldForwardToRippled(Web::Context const& ctx) const; + + bool + isClioOnly(std::string const& method) const; + + bool + validHandler(std::string const& method) const; +}; template void -logDuration(Context const& ctx, T const& dur) +logDuration(Web::Context const& ctx, T const& dur) { + using boost::json::serialize; + static clio::Logger log{"RPC"}; - std::stringstream ss; - ss << ctx.tag() - << "Request processing duration = " << std::chrono::duration_cast(dur).count() - << " milliseconds. request = " << ctx.params; - auto seconds = std::chrono::duration_cast(dur).count(); + auto const millis = std::chrono::duration_cast(dur).count(); + auto const seconds = std::chrono::duration_cast(dur).count(); + auto const msg = + fmt::format("Request processing duration = {} milliseconds. request = {}", millis, serialize(ctx.params)); + if (seconds > 10) - log.error() << ss.str(); + log.error() << ctx.tag() << msg; else if (seconds > 1) - log.warn() << ss.str(); + log.warn() << ctx.tag() << msg; else - log.info() << ss.str(); + log.info() << ctx.tag() << msg; } } // namespace RPC diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 0685a41b..990164df 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -484,7 +486,7 @@ parseStringAsUInt(std::string const& value) } std::variant -ledgerInfoFromRequest(Context const& ctx) +ledgerInfoFromRequest(std::shared_ptr const& backend, Web::Context const& ctx) { auto hashValue = ctx.params.contains("ledger_hash") ? ctx.params.at("ledger_hash") : nullptr; @@ -497,7 +499,7 @@ ledgerInfoFromRequest(Context const& ctx) if (!ledgerHash.parseHex(hashValue.as_string().c_str())) return Status{RippledError::rpcINVALID_PARAMS, "ledgerHashMalformed"}; - auto lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash, ctx.yield); + auto lgrInfo = backend->fetchLedgerByHash(ledgerHash, ctx.yield); if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence) return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; @@ -529,7 +531,7 @@ ledgerInfoFromRequest(Context const& ctx) if (!ledgerSequence) return Status{RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; - auto lgrInfo = ctx.backend->fetchLedgerBySequence(*ledgerSequence, ctx.yield); + auto lgrInfo = backend->fetchLedgerBySequence(*ledgerSequence, ctx.yield); if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence) return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; @@ -547,7 +549,7 @@ getLedgerInfoFromHashOrSeq( uint32_t maxSeq) { std::optional lgrInfo; - auto const err = RPC::Status{RPC::RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; + auto const err = Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; if (ledgerHash) { // invoke uint256's constructor to parse the hex string , instead of @@ -809,9 +811,13 @@ traverseOwnedNodes( } std::shared_ptr -read(ripple::Keylet const& keylet, ripple::LedgerInfo const& lgrInfo, Context const& context) +read( + std::shared_ptr const& backend, + ripple::Keylet const& keylet, + ripple::LedgerInfo const& lgrInfo, + Web::Context const& context) { - if (auto const blob = context.backend->fetchLedgerObject(keylet.key, lgrInfo.seq, context.yield); blob) + if (auto const blob = backend->fetchLedgerObject(keylet.key, lgrInfo.seq, context.yield); blob) { return std::make_shared(ripple::SerialIter{blob->data(), blob->size()}, keylet.key); } @@ -1458,10 +1464,10 @@ getNFTID(boost::json::object const& request) // is. Split it out into some helper functions. std::variant traverseTransactions( - Context const& context, + std::shared_ptr const& backend, + Web::Context const& context, std::function const& backend, - std::uint32_t const, bool const, std::optional const&, boost::asio::yield_context& yield)> transactionFetcher) @@ -1564,7 +1570,7 @@ traverseTransactions( if (request.contains(JS(ledger_index_max)) || request.contains(JS(ledger_index_min))) return Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}; - auto v = ledgerInfoFromRequest(context); + auto v = ledgerInfoFromRequest(backend, context); if (auto status = std::get_if(&v); status) return *status; @@ -1579,15 +1585,8 @@ traverseTransactions( cursor = {maxIndex, INT32_MAX}; } - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - if (request.contains(JS(limit))) - response[JS(limit)] = limit; - boost::json::array txns; - auto [blobs, retCursor] = transactionFetcher(context.backend, limit, forward, cursor, context.yield); + auto [blobs, retCursor] = transactionFetcher(backend, forward, cursor, context.yield); auto timeDiff = util::timed([&, &retCursor = retCursor, &blobs = blobs]() { if (retCursor) { diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index b62952ee..fcd1c052 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -28,18 +28,13 @@ #include #include #include -#include #include +#include #include - -// Useful macro for borrowing from ripple::jss -// static strings. (J)son (S)trings -#define JS(x) ripple::jss::x.c_str() - -// Access (SF)ield name (S)trings -#define SFS(x) ripple::x.jsonName.c_str() +#include namespace RPC { + std::optional accountFromStringStrict(std::string const& account); @@ -94,7 +89,7 @@ generatePubLedgerMessage( std::uint32_t txnCount); std::variant -ledgerInfoFromRequest(Context const& ctx); +ledgerInfoFromRequest(std::shared_ptr const& backend, Web::Context const& ctx); std::variant getLedgerInfoFromHashOrSeq( @@ -139,7 +134,11 @@ ngTraverseOwnedNodes( std::function atOwnedNode); std::shared_ptr -read(ripple::Keylet const& keylet, ripple::LedgerInfo const& lgrInfo, Context const& context); +read( + std::shared_ptr const& backend, + ripple::Keylet const& keylet, + ripple::LedgerInfo const& lgrInfo, + Web::Context const& context); std::variant> keypairFromRequst(boost::json::object const& request); @@ -274,15 +273,12 @@ getNFTID(boost::json::object const& request); // be used for any future transaction enumeration APIs. std::variant traverseTransactions( - Context const& context, + std::shared_ptr const& backend, + Web::Context const& context, std::function const& backend, - std::uint32_t const, bool const, std::optional const&, boost::asio::yield_context& yield)> transactionFetcher); -[[nodiscard]] boost::json::object const -computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector const& transactions); - } // namespace RPC diff --git a/src/rpc/WorkQueue.cpp b/src/rpc/WorkQueue.cpp index 7565dc45..3de6226f 100644 --- a/src/rpc/WorkQueue.cpp +++ b/src/rpc/WorkQueue.cpp @@ -23,8 +23,7 @@ WorkQueue::WorkQueue(std::uint32_t numWorkers, uint32_t maxSize) { if (maxSize != 0) maxSize_ = maxSize; + while (--numWorkers) - { threads_.emplace_back([this] { ioc_.run(); }); - } } diff --git a/src/rpc/WorkQueue.h b/src/rpc/WorkQueue.h index fc943336..65a40b39 100644 --- a/src/rpc/WorkQueue.h +++ b/src/rpc/WorkQueue.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include @@ -41,9 +42,25 @@ class WorkQueue uint32_t maxSize_ = std::numeric_limits::max(); clio::Logger log_{"RPC"}; + std::vector threads_ = {}; + boost::asio::io_context ioc_ = {}; + std::optional work_{ioc_}; + public: WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0); + static WorkQueue + make_WorkQueue(clio::Config const& config) + { + static clio::Logger log{"RPC"}; + auto const serverConfig = config.section("server"); + auto const numThreads = config.valueOr("workers", std::thread::hardware_concurrency()); + auto const maxQueueSize = serverConfig.valueOr("max_queue_size", 0); // 0 is no limit + + log.info() << "Number of workers = " << numThreads << ". Max queue size = " << maxQueueSize; + return WorkQueue{numThreads, maxQueueSize}; + } + template bool postCoro(F&& f, bool isWhiteListed) @@ -53,38 +70,39 @@ public: log_.warn() << "Queue is full. rejecting job. current size = " << curSize_ << " max size = " << maxSize_; return false; } + ++curSize_; auto start = std::chrono::system_clock::now(); - // Each time we enqueue a job, we want to post a symmetrical job that - // will dequeue and run the job at the front of the job queue. - boost::asio::spawn(ioc_, [this, f = std::move(f), start](boost::asio::yield_context yield) { - auto run = std::chrono::system_clock::now(); - auto wait = std::chrono::duration_cast(run - start).count(); - // increment queued_ here, in the same place we implement - // durationUs_ + + // Each time we enqueue a job, we want to post a symmetrical job that will dequeue and run the job at the front + // of the job queue. + boost::asio::spawn(ioc_, [this, f = std::move(f), start](auto yield) { + auto const run = std::chrono::system_clock::now(); + auto const wait = std::chrono::duration_cast(run - start).count(); + + // increment queued_ here, in the same place we implement durationUs_ ++queued_; durationUs_ += wait; log_.info() << "WorkQueue wait time = " << wait << " queue size = " << curSize_; + f(yield); + --curSize_; }); + return true; } boost::json::object report() const { - boost::json::object obj; + auto obj = boost::json::object{}; + obj["queued"] = queued_; obj["queued_duration_us"] = durationUs_; obj["current_queue_size"] = curSize_; obj["max_queue_size"] = maxSize_; + return obj; } - -private: - std::vector threads_ = {}; - - boost::asio::io_context ioc_ = {}; - std::optional work_{ioc_}; }; diff --git a/src/rpc/common/AnyHandler.h b/src/rpc/common/AnyHandler.h index c1639391..7b14d449 100644 --- a/src/rpc/common/AnyHandler.h +++ b/src/rpc/common/AnyHandler.h @@ -23,7 +23,7 @@ #include #include -namespace RPCng { +namespace RPC { /** * @brief A type-erased Handler that can contain any (NextGen) RPC handler class @@ -39,10 +39,8 @@ public: * @brief Type-erases any handler class. * * @tparam HandlerType The real type of wrapped handler class - * @tparam ProcessingStrategy A strategy that implements how processing of - * JSON is to be done - * @param handler The handler to wrap. Required to fulfil the @ref Handler - * concept. + * @tparam ProcessingStrategy A strategy that implements how processing of JSON is to be done + * @param handler The handler to wrap. Required to fulfil the @ref Handler concept. */ template > /* implicit */ AnyHandler(HandlerType&& handler) @@ -54,6 +52,7 @@ public: AnyHandler(AnyHandler const& other) : pimpl_{other.pimpl_->clone()} { } + AnyHandler& operator=(AnyHandler const& rhs) { @@ -61,6 +60,7 @@ public: pimpl_.swap(copy.pimpl_); return *this; } + AnyHandler(AnyHandler&&) = default; AnyHandler& operator=(AnyHandler&&) = default; @@ -69,7 +69,7 @@ public: * @brief Process incoming JSON by the stored handler * * @param value The JSON to process - * @return JSON result or @ref RPC::Status on error + * @return JSON result or @ref Status on error */ [[nodiscard]] ReturnType process(boost::json::value const& value) const @@ -78,11 +78,10 @@ public: } /** - * @brief Process incoming JSON by the stored handler in a provided - * coroutine + * @brief Process incoming JSON by the stored handler in a provided coroutine * * @param value The JSON to process - * @return JSON result or @ref RPC::Status on error + * @return JSON result or @ref Status on error */ [[nodiscard]] ReturnType process(boost::json::value const& value, Context const& ctx) const @@ -138,4 +137,4 @@ private: std::unique_ptr pimpl_; }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/common/Concepts.h b/src/rpc/common/Concepts.h index a1757707..08c2e8c8 100644 --- a/src/rpc/common/Concepts.h +++ b/src/rpc/common/Concepts.h @@ -26,7 +26,7 @@ #include -namespace RPCng { +namespace RPC { struct RpcSpec; @@ -84,4 +84,5 @@ concept Handler = ) and boost::json::has_value_from::value; // clang-format on -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/common/Specs.cpp b/src/rpc/common/Specs.cpp index 9fdc5d32..9918eb84 100644 --- a/src/rpc/common/Specs.cpp +++ b/src/rpc/common/Specs.cpp @@ -21,7 +21,7 @@ #include -namespace RPCng { +namespace RPC { [[nodiscard]] MaybeError FieldSpec::validate(boost::json::value const& value) const @@ -39,4 +39,4 @@ RpcSpec::validate(boost::json::value const& value) const return {}; } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/common/Specs.h b/src/rpc/common/Specs.h index 309d91d2..b3188f98 100644 --- a/src/rpc/common/Specs.h +++ b/src/rpc/common/Specs.h @@ -26,7 +26,7 @@ #include #include -namespace RPCng { +namespace RPC { /** * @brief Represents a Specification for one field of an RPC command @@ -51,7 +51,7 @@ struct FieldSpec final * @brief Validates the passed JSON value using the stored requirements * * @param value The JSON value to validate - * @return Nothing on success; @ref RPC::Status on error + * @return Nothing on success; @ref Status on error */ [[nodiscard]] MaybeError validate(boost::json::value const& value) const; @@ -81,7 +81,7 @@ struct RpcSpec final * @brief Validates the passed JSON value using the stored field specs * * @param value The JSON value to validate - * @return Nothing on success; @ref RPC::Status on error + * @return Nothing on success; @ref Status on error */ [[nodiscard]] MaybeError validate(boost::json::value const& value) const; @@ -90,4 +90,4 @@ private: std::vector fields_; }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/common/Types.h b/src/rpc/common/Types.h index dca3482b..6878b9be 100644 --- a/src/rpc/common/Types.h +++ b/src/rpc/common/Types.h @@ -27,29 +27,30 @@ class WsBase; class SubscriptionManager; -namespace RPCng { + +namespace RPC { /** * @brief Return type used for Validators that can return error but don't have * specific value to return */ -using MaybeError = util::Expected; +using MaybeError = util::Expected; /** * @brief The type that represents just the error part of @ref MaybeError */ -using Error = util::Unexpected; +using Error = util::Unexpected; /** * @brief Return type for each individual handler */ template -using HandlerReturnType = util::Expected; +using HandlerReturnType = util::Expected; /** * @brief The final return type out of RPC engine */ -using ReturnType = util::Expected; +using ReturnType = util::Expected; struct RpcSpec; struct FieldSpec; @@ -70,10 +71,27 @@ struct Context std::string clientIp; }; +class AnyHandler; + +class HandlerProvider +{ +public: + virtual ~HandlerProvider() = default; + + virtual bool + contains(std::string const& method) const = 0; + + virtual std::optional + getHandler(std::string const& command) const = 0; + + virtual bool + isClioOnly(std::string const& command) const = 0; +}; + inline void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, VoidOutput const&) { jv = boost::json::object{}; } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index fcda53e1..52c43ce2 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -26,7 +26,7 @@ #include #include -namespace RPCng::validation { +namespace RPC::validation { [[nodiscard]] MaybeError Section::verify(boost::json::value const& value, std::string_view key) const @@ -36,14 +36,17 @@ Section::verify(boost::json::value const& value, std::string_view key) const // instead auto const& res = value.at(key.data()); + // if it is not a json object, let other validators fail if (!res.is_object()) return {}; + for (auto const& spec : specs) { if (auto const ret = spec.validate(res); not ret) return Error{ret.error()}; } + return {}; } @@ -51,8 +54,7 @@ Section::verify(boost::json::value const& value, std::string_view key) const Required::verify(boost::json::value const& value, std::string_view key) const { if (not value.is_object() or not value.as_object().contains(key.data())) - return Error{ - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"}}; return {}; } @@ -65,11 +67,11 @@ ValidateArrayAt::verify(boost::json::value const& value, std::string_view key) c // instead if (not value.as_object().at(key.data()).is_array()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; auto const& arr = value.as_object().at(key.data()).as_array(); if (idx_ >= arr.size()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; auto const& res = arr.at(idx_); for (auto const& spec : specs_) @@ -94,107 +96,107 @@ checkIsU32Numeric(std::string_view sv) { uint32_t unused; auto [_, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), unused); + return ec == std::errc(); } CustomValidator Uint256HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + ripple::uint256 ledgerHash; if (!ledgerHash.parseHex(value.as_string().c_str())) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + return MaybeError{}; }}; CustomValidator LedgerIndexValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - auto err = Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}}; + auto err = Error{Status{RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}}; + if (!value.is_string() && !(value.is_uint64() || value.is_int64())) - { return err; - } + if (value.is_string() && value.as_string() != "validated" && !checkIsU32Numeric(value.as_string().c_str())) - { return err; - } + return MaybeError{}; }}; CustomValidator AccountValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + // TODO: we are using accountFromStringStrict from RPCHelpers, after we // remove all old handler, this function can be moved to here - if (!RPC::accountFromStringStrict(value.as_string().c_str())) - { - return Error{RPC::Status{RPC::RippledError::rpcACT_MALFORMED, std::string(key) + "Malformed"}}; - } + if (!accountFromStringStrict(value.as_string().c_str())) + return Error{Status{RippledError::rpcACT_MALFORMED, std::string(key) + "Malformed"}}; + return MaybeError{}; }}; CustomValidator AccountBase58Validator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + auto const account = ripple::parseBase58(value.as_string().c_str()); if (!account || account->isZero()) - return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS}}; + return Error{Status{ClioError::rpcMALFORMED_ADDRESS}}; + return MaybeError{}; }}; CustomValidator AccountMarkerValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + // TODO: we are using parseAccountCursor from RPCHelpers, after we // remove all old handler, this function can be moved to here - if (!RPC::parseAccountCursor(value.as_string().c_str())) + if (!parseAccountCursor(value.as_string().c_str())) { // align with the current error message - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "Malformed cursor"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor"}}; } + return MaybeError{}; }}; CustomValidator CurrencyValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + ripple::Currency currency; if (!ripple::to_currency(currency, value.as_string().c_str())) - return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"}}; + return Error{Status{ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"}}; + return MaybeError{}; }}; CustomValidator IssuerValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + ripple::AccountID issuer; + + // TODO: need to align with the error if (!ripple::to_issuer(issuer, value.as_string().c_str())) - return Error{RPC::Status{// TODO: need to align with the error - RPC::RippledError::rpcINVALID_PARAMS, - fmt::format("Invalid field '{}', bad issuer.", key)}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', bad issuer.", key)}}; if (issuer == ripple::noAccount()) - return Error{RPC::Status{ - RPC::RippledError::rpcINVALID_PARAMS, + return Error{Status{ + RippledError::rpcINVALID_PARAMS, fmt::format( "Invalid field '{}', bad issuer account " "one.", key)}}; + return MaybeError{}; }}; @@ -203,34 +205,40 @@ CustomValidator SubscribeStreamValidator = static std::unordered_set const validStreams = { "ledger", "transactions", "transactions_proposed", "book_changes", "manifests", "validations"}; if (!value.is_array()) - { - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + for (auto const& v : value.as_array()) { if (!v.is_string()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "streamNotString"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "streamNotString"}}; + if (not validStreams.contains(v.as_string().c_str())) - return Error{RPC::Status{RPC::RippledError::rpcSTREAM_MALFORMED}}; + return Error{Status{RippledError::rpcSTREAM_MALFORMED}}; } + return MaybeError{}; }}; CustomValidator SubscribeAccountsValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_array()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + if (value.as_array().size() == 0) - return Error{RPC::Status{RPC::RippledError::rpcACT_MALFORMED, std::string(key) + " malformed."}}; + return Error{Status{RippledError::rpcACT_MALFORMED, std::string(key) + " malformed."}}; for (auto const& v : value.as_array()) { auto obj = boost::json::object(); auto const keyItem = std::string(key) + "'sItem"; + obj[keyItem] = v; + if (auto const err = AccountValidator.verify(obj, keyItem); !err) return err; } + return MaybeError{}; }}; -} // namespace RPCng::validation + +} // namespace RPC::validation diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index 96f975ac..699fff33 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -25,7 +25,7 @@ #include -namespace RPCng::validation { +namespace RPC::validation { /** * @brief Check that the type is the same as what was expected @@ -134,8 +134,8 @@ public: using boost::json::value_to; auto const res = value_to(value.as_object().at(key.data())); if (value_ == res) - return Error{RPC::Status{ - RPC::RippledError::rpcNOT_SUPPORTED, + return Error{Status{ + RippledError::rpcNOT_SUPPORTED, fmt::format("Not supported field '{}'s value '{}'", std::string{key}, res)}}; } return {}; @@ -157,9 +157,8 @@ public: verify(boost::json::value const& value, std::string_view key) const { if (value.is_object() and value.as_object().contains(key.data())) - { - return Error{RPC::Status{RPC::RippledError::rpcNOT_SUPPORTED, "Not supported field '" + std::string{key}}}; - } + return Error{Status{RippledError::rpcNOT_SUPPORTED, "Not supported field '" + std::string{key}}}; + return {}; } }; @@ -192,7 +191,7 @@ struct Type final auto const convertible = (checkType(res) || ...); if (not convertible) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; return {}; } @@ -228,16 +227,18 @@ public: [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { + using boost::json::value_to; + if (not value.is_object() or not value.as_object().contains(key.data())) return {}; // ignore. field does not exist, let 'required' fail // instead - using boost::json::value_to; auto const res = value_to(value.as_object().at(key.data())); - // todo: may want a way to make this code more generic (e.g. use a free + + // TODO: may want a way to make this code more generic (e.g. use a free // function that can be overridden for this comparison) if (res < min_ || res > max_) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; return {}; } @@ -271,14 +272,15 @@ public: [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { + using boost::json::value_to; + if (not value.is_object() or not value.as_object().contains(key.data())) return {}; // ignore. field does not exist, let 'required' fail // instead - using boost::json::value_to; auto const res = value_to(value.as_object().at(key.data())); if (res != original_) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; return {}; } @@ -318,14 +320,15 @@ public: [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { + using boost::json::value_to; + if (not value.is_object() or not value.as_object().contains(key.data())) return {}; // ignore. field does not exist, let 'required' fail // instead - using boost::json::value_to; auto const res = value_to(value.as_object().at(key.data())); if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_)) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; + return Error{Status{RippledError::rpcINVALID_PARAMS}}; return {}; } @@ -387,7 +390,7 @@ public: { validator_ = [... r = std::forward(requirements)]( boost::json::value const& j, std::string_view key) -> MaybeError { - std::optional firstFailure = std::nullopt; + std::optional firstFailure = std::nullopt; // the check logic is the same as fieldspec // clang-format off @@ -439,14 +442,14 @@ template class WithCustomError final { Requirement requirement; - RPC::Status error; + Status error; public: /** * @brief Constructs a validator that calls the given validator "req" and * return customized error "err" */ - WithCustomError(Requirement req, RPC::Status err) : requirement{std::move(req)}, error{err} + WithCustomError(Requirement req, Status err) : requirement{std::move(req)}, error{err} { } @@ -555,4 +558,4 @@ extern CustomValidator SubscribeStreamValidator; */ extern CustomValidator SubscribeAccountsValidator; -} // namespace RPCng::validation +} // namespace RPC::validation diff --git a/src/rpc/common/impl/Factories.h b/src/rpc/common/impl/Factories.h index 6a6081bb..adcbcfb5 100644 --- a/src/rpc/common/impl/Factories.h +++ b/src/rpc/common/impl/Factories.h @@ -26,14 +26,14 @@ #include -namespace RPCng::detail { +namespace RPC::detail { template [[nodiscard]] auto makeFieldValidator(std::string const& key, Requirements&&... requirements) { return [key, ... r = std::forward(requirements)](boost::json::value const& j) -> MaybeError { - std::optional firstFailure = std::nullopt; + std::optional firstFailure = std::nullopt; // This expands in order of Requirements and stops evaluating after // first failure which is stored in `firstFailure` and can be checked @@ -55,4 +55,4 @@ makeFieldValidator(std::string const& key, Requirements&&... requirements) }; } -} // namespace RPCng::detail +} // namespace RPC::detail diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp new file mode 100644 index 00000000..41b8823f --- /dev/null +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RPC::detail { + +ProductionHandlerProvider::ProductionHandlerProvider( + std::shared_ptr const& backend, + std::shared_ptr const& subscriptionManager, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + Counters const& counters) + : handlerMap_{ + {"account_channels", {AccountChannelsHandler{backend}}}, + {"account_currencies", {AccountCurrenciesHandler{backend}}}, + {"account_info", {AccountInfoHandler{backend}}}, + {"account_lines", {AccountLinesHandler{backend}}}, + {"account_nfts", {AccountNFTsHandler{backend}}}, + {"account_objects", {AccountObjectsHandler{backend}}}, + {"account_offers", {AccountOffersHandler{backend}}}, + {"account_tx", {AccountTxHandler{backend}}}, + {"book_changes", {BookChangesHandler{backend}}}, + {"book_offers", {BookOffersHandler{backend}}}, + {"gateway_balances", {GatewayBalancesHandler{backend}}}, + {"ledger", {LedgerHandler{backend}}}, + {"ledger_data", {LedgerDataHandler{backend}}}, + {"ledger_entry", {LedgerEntryHandler{backend}}}, + {"ledger_range", {LedgerRangeHandler{backend}}}, + {"nft_history", {NFTHistoryHandler{backend}, true}}, // clio only + {"nft_buy_offers", {NFTBuyOffersHandler{backend}}}, + {"nft_info", {NFTInfoHandler{backend}, true}}, // clio only + {"nft_sell_offers", {NFTSellOffersHandler{backend}}}, + {"noripple_check", {NoRippleCheckHandler{backend}}}, + {"ping", {PingHandler{}}}, + {"random", {RandomHandler{}}}, + {"server_info", {ServerInfoHandler{backend, subscriptionManager, balancer, etl, counters}}}, + {"transaction_entry", {TransactionEntryHandler{backend}}}, + {"tx", {TxHandler{backend}}}, + {"subscribe", {SubscribeHandler{backend, subscriptionManager}}}, + {"unsubscribe", {UnsubscribeHandler{backend, subscriptionManager}}}, + } +{ +} + +bool +ProductionHandlerProvider::contains(std::string const& method) const +{ + return handlerMap_.contains(method); +} + +std::optional +ProductionHandlerProvider::getHandler(std::string const& command) const +{ + if (!handlerMap_.contains(command)) + return {}; + + return handlerMap_.at(command).handler; +} + +bool +ProductionHandlerProvider::isClioOnly(std::string const& command) const +{ + return handlerMap_.contains(command) && handlerMap_.at(command).isClioOnly; +} + +} // namespace RPC::detail diff --git a/src/rpc/common/impl/HandlerProvider.h b/src/rpc/common/impl/HandlerProvider.h new file mode 100644 index 00000000..8144af53 --- /dev/null +++ b/src/rpc/common/impl/HandlerProvider.h @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and 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. +*/ +//============================================================================== + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +class SubscriptionManager; +class ReportingETL; +class ETLLoadBalancer; + +namespace RPC { +class Counters; +} + +namespace RPC::detail { + +class ProductionHandlerProvider final : public HandlerProvider +{ + struct Handler + { + AnyHandler handler; + bool isClioOnly; + + /* implicit */ Handler(AnyHandler handler, bool clioOnly = false) : handler{handler}, isClioOnly{clioOnly} + { + } + }; + + std::unordered_map handlerMap_; + +public: + ProductionHandlerProvider( + std::shared_ptr const& backend, + std::shared_ptr const& subscriptionManager, + std::shared_ptr const& balancer, + std::shared_ptr const& etl, + Counters const& counters); + + bool + contains(std::string const& method) const override; + + std::optional + getHandler(std::string const& command) const override; + + bool + isClioOnly(std::string const& command) const override; +}; + +} // namespace RPC::detail diff --git a/src/rpc/common/impl/Processors.h b/src/rpc/common/impl/Processors.h index a60cf923..2ec201be 100644 --- a/src/rpc/common/impl/Processors.h +++ b/src/rpc/common/impl/Processors.h @@ -22,7 +22,7 @@ #include #include -namespace RPCng::detail { +namespace RPC::detail { template static constexpr bool unsupported_handler_v = false; @@ -91,4 +91,4 @@ struct DefaultProcessor final } }; -} // namespace RPCng::detail +} // namespace RPC::detail diff --git a/src/rpc/handlers/AccountChannels.cpp b/src/rpc/handlers/AccountChannels.cpp index 87221cbc..dfc6002d 100644 --- a/src/rpc/handlers/AccountChannels.cpp +++ b/src/rpc/handlers/AccountChannels.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,118 +17,170 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include namespace RPC { void -addChannel(boost::json::array& jsonLines, ripple::SLE const& line) +AccountChannelsHandler::addChannel(std::vector& jsonChannels, ripple::SLE const& channelSle) const { - boost::json::object jDst; - jDst[JS(channel_id)] = ripple::to_string(line.key()); - jDst[JS(account)] = ripple::to_string(line.getAccountID(ripple::sfAccount)); - jDst[JS(destination_account)] = ripple::to_string(line.getAccountID(ripple::sfDestination)); - jDst[JS(amount)] = line[ripple::sfAmount].getText(); - jDst[JS(balance)] = line[ripple::sfBalance].getText(); - if (publicKeyType(line[ripple::sfPublicKey])) - { - ripple::PublicKey const pk(line[ripple::sfPublicKey]); - jDst[JS(public_key)] = toBase58(ripple::TokenType::AccountPublic, pk); - jDst[JS(public_key_hex)] = strHex(pk); - } - jDst[JS(settle_delay)] = line[ripple::sfSettleDelay]; - if (auto const& v = line[~ripple::sfExpiration]) - jDst[JS(expiration)] = *v; - if (auto const& v = line[~ripple::sfCancelAfter]) - jDst[JS(cancel_after)] = *v; - if (auto const& v = line[~ripple::sfSourceTag]) - jDst[JS(source_tag)] = *v; - if (auto const& v = line[~ripple::sfDestinationTag]) - jDst[JS(destination_tag)] = *v; + ChannelResponse channel; + channel.channelID = ripple::to_string(channelSle.key()); + channel.account = ripple::to_string(channelSle.getAccountID(ripple::sfAccount)); + channel.accountDestination = ripple::to_string(channelSle.getAccountID(ripple::sfDestination)); + channel.amount = channelSle[ripple::sfAmount].getText(); + channel.balance = channelSle[ripple::sfBalance].getText(); + channel.settleDelay = channelSle[ripple::sfSettleDelay]; - jsonLines.push_back(jDst); + if (publicKeyType(channelSle[ripple::sfPublicKey])) + { + ripple::PublicKey const pk(channelSle[ripple::sfPublicKey]); + channel.publicKey = toBase58(ripple::TokenType::AccountPublic, pk); + channel.publicKeyHex = strHex(pk); + } + + if (auto const& v = channelSle[~ripple::sfExpiration]) + channel.expiration = *v; + + if (auto const& v = channelSle[~ripple::sfCancelAfter]) + channel.cancelAfter = *v; + + if (auto const& v = channelSle[~ripple::sfSourceTag]) + channel.sourceTag = *v; + + if (auto const& v = channelSle[~ripple::sfDestinationTag]) + channel.destinationTag = *v; + + jsonChannels.push_back(channel); } -Result -doAccountChannels(Context const& context) +AccountChannelsHandler::Result +AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - auto rawAcct = - context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); - - if (!rawAcct) - return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; - - ripple::AccountID destAccount; - if (auto const status = getAccount(request, destAccount, JS(destination_account)); status) - return status; - - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - std::optional marker = {}; - if (request.contains(JS(marker))) - { - if (!request.at(JS(marker)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - - marker = request.at(JS(marker)).as_string().c_str(); - } - - response[JS(account)] = ripple::to_string(accountID); - response[JS(channels)] = boost::json::value(boost::json::array_kind); - response[JS(limit)] = limit; - boost::json::array& jsonChannels = response.at(JS(channels)).as_array(); + auto const destAccountID = input.destinationAccount ? accountFromStringStrict(input.destinationAccount.value()) + : std::optional{}; + Output response; auto const addToResponse = [&](ripple::SLE&& sle) { if (sle.getType() == ripple::ltPAYCHAN && sle.getAccountID(ripple::sfAccount) == accountID && - (!destAccount || destAccount == sle.getAccountID(ripple::sfDestination))) + (!destAccountID || *destAccountID == sle.getAccountID(ripple::sfDestination))) { - addChannel(jsonChannels, sle); + addChannel(response.channels, sle); } return true; }; - auto next = - traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); + auto const next = ngTraverseOwnedNodes( + *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - - if (auto status = std::get_if(&next)) - return *status; - - auto nextMarker = std::get(next); + response.account = input.account; + response.limit = input.limit; + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; + auto const nextMarker = std::get(next); if (nextMarker.isNonZero()) - response[JS(marker)] = nextMarker.toString(); + response.marker = nextMarker.toString(); return response; } +AccountChannelsHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountChannelsHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jv.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(limit))) + input.limit = jv.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = jv.at(JS(marker)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(destination_account))) + input.destinationAccount = jv.at(JS(destination_account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountChannelsHandler::Output const& output) +{ + auto obj = boost::json::object{ + {JS(account), output.account}, + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(limit), output.limit}, + {JS(channels), output.channels}, + }; + + if (output.marker) + obj[JS(marker)] = output.marker.value(); + + jv = std::move(obj); +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountChannelsHandler::ChannelResponse const& channel) +{ + auto obj = boost::json::object{ + {JS(channel_id), channel.channelID}, + {JS(account), channel.account}, + {JS(destination_account), channel.accountDestination}, + {JS(amount), channel.amount}, + {JS(balance), channel.balance}, + {JS(settle_delay), channel.settleDelay}, + }; + + if (channel.publicKey) + obj[JS(public_key)] = *(channel.publicKey); + + if (channel.publicKeyHex) + obj[JS(public_key_hex)] = *(channel.publicKeyHex); + + if (channel.expiration) + obj[JS(expiration)] = *(channel.expiration); + + if (channel.cancelAfter) + obj[JS(cancel_after)] = *(channel.cancelAfter); + + if (channel.sourceTag) + obj[JS(source_tag)] = *(channel.sourceTag); + + if (channel.destinationTag) + obj[JS(destination_tag)] = *(channel.destinationTag); + + jv = std::move(obj); +} } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountChannels.h b/src/rpc/handlers/AccountChannels.h similarity index 96% rename from src/rpc/ngHandlers/AccountChannels.h rename to src/rpc/handlers/AccountChannels.h index 89e082a4..32c817af 100644 --- a/src/rpc/ngHandlers/AccountChannels.h +++ b/src/rpc/handlers/AccountChannels.h @@ -20,13 +20,13 @@ #pragma once #include -#include +#include #include #include #include -namespace RPCng { +namespace RPC { class AccountChannelsHandler { // dependencies @@ -72,7 +72,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountChannelsHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -110,4 +110,4 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ChannelResponse const& channel); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 656816bc..f1094a65 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,90 +17,99 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include namespace RPC { - -Result -doAccountCurrencies(Context const& context) +AccountCurrenciesHandler::Result +AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - auto rawAcct = - context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); - - if (!rawAcct) - return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; - - std::set send, receive; + Output response; auto const addToResponse = [&](ripple::SLE&& sle) { if (sle.getType() == ripple::ltRIPPLE_STATE) { - ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); + auto balance = sle.getFieldAmount(ripple::sfBalance); + auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit); + auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit); + bool const viewLowest = (lowLimit.getIssuer() == accountID); + auto const lineLimit = viewLowest ? lowLimit : highLimit; + auto const lineLimitPeer = !viewLowest ? lowLimit : highLimit; - auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit); - auto highLimit = sle.getFieldAmount(ripple::sfHighLimit); - bool viewLowest = (lowLimit.getIssuer() == accountID); - auto lineLimit = viewLowest ? lowLimit : highLimit; - auto lineLimitPeer = !viewLowest ? lowLimit : highLimit; if (!viewLowest) balance.negate(); if (balance < lineLimit) - receive.insert(ripple::to_string(balance.getCurrency())); + response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency())); + if ((-balance) < lineLimitPeer) - send.insert(ripple::to_string(balance.getCurrency())); + response.sendCurrencies.insert(ripple::to_string(balance.getCurrency())); } return true; }; - traverseOwnedNodes( - *context.backend, - accountID, + // traverse all owned nodes, limit->max, marker->empty + ngTraverseOwnedNodes( + *sharedPtrBackend_, + *accountID, lgrInfo.seq, std::numeric_limits::max(), {}, - context.yield, + ctx.yield, addToResponse); - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - - response[JS(receive_currencies)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonReceive = response.at(JS(receive_currencies)).as_array(); - - for (auto const& currency : receive) - jsonReceive.push_back(currency.c_str()); - - response[JS(send_currencies)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonSend = response.at(JS(send_currencies)).as_array(); - - for (auto const& currency : send) - jsonSend.push_back(currency.c_str()); + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; return response; } +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountCurrenciesHandler::Output const& output) +{ + jv = { + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(receive_currencies), output.receiveCurrencies}, + {JS(send_currencies), output.sendCurrencies}, + }; +} + +AccountCurrenciesHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountCurrenciesHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jv.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountCurrencies.h b/src/rpc/handlers/AccountCurrencies.h similarity index 94% rename from src/rpc/ngHandlers/AccountCurrencies.h rename to src/rpc/handlers/AccountCurrencies.h index 987cdd90..9dd0089b 100644 --- a/src/rpc/ngHandlers/AccountCurrencies.h +++ b/src/rpc/handlers/AccountCurrencies.h @@ -26,7 +26,7 @@ #include -namespace RPCng { +namespace RPC { class AccountCurrenciesHandler { // dependencies @@ -43,7 +43,7 @@ public: bool validated = true; }; - // TODO:we did not implement the "strict" field + // TODO: We did not implement the "strict" field (can't be implemented?) struct Input { std::string account; @@ -51,7 +51,7 @@ public: std::optional ledgerIndex; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountCurrenciesHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -80,4 +80,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index fc7a4b14..4b92aef6 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,98 +17,110 @@ */ //============================================================================== -#include -#include -#include - -#include -#include - -// { -// account: , -// strict: // optional (default false) -// // if true only allow public keys and addresses. -// ledger_hash : -// ledger_index : -// signer_lists : // optional (default false) -// // if true return SignerList(s). -// queue : // optional (default false) -// // if true return information about transactions -// // in the current TxQ, only if the requested -// // ledger is open. Otherwise if true, returns an -// // error. -// } +#include namespace RPC { - -Result -doAccountInfo(Context const& context) +AccountInfoHandler::Result +AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + if (!input.account && !input.ident) + return Error{Status{RippledError::rpcACT_MALFORMED}}; - std::string strIdent; - if (request.contains(JS(account))) - strIdent = request.at(JS(account)).as_string().c_str(); - else if (request.contains(JS(ident))) - strIdent = request.at(JS(ident)).as_string().c_str(); - else - return Status{RippledError::rpcACT_MALFORMED}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - // We only need to fetch the ledger header because the ledger hash is - // supposed to be included in the response. The ledger sequence is specified - // in the request - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountStr = input.account.value_or(input.ident.value_or("")); + auto const accountID = accountFromStringStrict(accountStr); + auto const accountKeylet = ripple::keylet::account(*accountID); + auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield); - // Get info on account. - auto accountID = accountFromStringStrict(strIdent); - if (!accountID) - return Status{RippledError::rpcACT_MALFORMED}; + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - auto key = ripple::keylet::account(accountID.value()); - std::optional> dbResponse = - context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield); + ripple::STLedgerEntry const sle{ + ripple::SerialIter{accountLedgerObject->data(), accountLedgerObject->size()}, accountKeylet.key}; - if (!dbResponse) - return Status{RippledError::rpcACT_NOT_FOUND}; - - ripple::STLedgerEntry sle{ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key}; - - if (!key.check(sle)) - return Status{RippledError::rpcDB_DESERIALIZATION}; - - response[JS(account_data)] = toJson(sle); - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; + if (!accountKeylet.check(sle)) + return Error{Status{RippledError::rpcDB_DESERIALIZATION}}; // Return SignerList(s) if that is requested. - if (request.contains(JS(signer_lists)) && request.at(JS(signer_lists)).as_bool()) + if (input.signerLists) { // We put the SignerList in an array because of an anticipated // future when we support multiple signer lists on one account. - boost::json::array signerList; - auto signersKey = ripple::keylet::signers(*accountID); + auto const signersKey = ripple::keylet::signers(*accountID); // This code will need to be revisited if in the future we // support multiple SignerLists on one account. - auto const signers = context.backend->fetchLedgerObject(signersKey.key, lgrInfo.seq, context.yield); + auto const signers = sharedPtrBackend_->fetchLedgerObject(signersKey.key, lgrInfo.seq, ctx.yield); + std::vector signerList; + if (signers) { - ripple::STLedgerEntry sleSigners{ripple::SerialIter{signers->data(), signers->size()}, signersKey.key}; - if (!signersKey.check(sleSigners)) - return Status{RippledError::rpcDB_DESERIALIZATION}; + ripple::STLedgerEntry const sleSigners{ + ripple::SerialIter{signers->data(), signers->size()}, signersKey.key}; - signerList.push_back(toJson(sleSigners)); + if (!signersKey.check(sleSigners)) + return Error{Status{RippledError::rpcDB_DESERIALIZATION}}; + + signerList.push_back(sleSigners); } - response[JS(account_data)].as_object()[JS(signer_lists)] = std::move(signerList); + return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, signerList); } - return response; + return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle); +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(account_data), toJson(output.accountData)}, + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + }; + + if (output.signerLists) + { + jv.as_object()[JS(signer_lists)] = boost::json::array(); + for (auto const& signerList : output.signerLists.value()) + jv.as_object()[JS(signer_lists)].as_array().push_back(toJson(signerList)); + } +} + +AccountInfoHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountInfoHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(ident))) + input.ident = jsonObject.at(JS(ident)).as_string().c_str(); + + if (jsonObject.contains(JS(account))) + input.account = jsonObject.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(signer_lists))) + input.signerLists = jsonObject.at(JS(signer_lists)).as_bool(); + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountInfo.h b/src/rpc/handlers/AccountInfo.h similarity index 97% rename from src/rpc/ngHandlers/AccountInfo.h rename to src/rpc/handlers/AccountInfo.h index 5035cb18..ec0e02d4 100644 --- a/src/rpc/ngHandlers/AccountInfo.h +++ b/src/rpc/handlers/AccountInfo.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class AccountInfoHandler { std::shared_ptr sharedPtrBackend_; @@ -68,7 +68,7 @@ public: bool signerLists = false; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountInfoHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -98,4 +98,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index ead52327..4716224e 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,146 +17,107 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#include +#include namespace RPC { void -addLine( - boost::json::array& jsonLines, - ripple::SLE const& line, +AccountLinesHandler::addLine( + std::vector& lines, + ripple::SLE const& lineSle, ripple::AccountID const& account, - std::optional const& peerAccount) + std::optional const& peerAccount) const { - auto flags = line.getFieldU32(ripple::sfFlags); - auto lowLimit = line.getFieldAmount(ripple::sfLowLimit); - auto highLimit = line.getFieldAmount(ripple::sfHighLimit); - auto lowID = lowLimit.getIssuer(); - auto highID = highLimit.getIssuer(); - auto lowQualityIn = line.getFieldU32(ripple::sfLowQualityIn); - auto lowQualityOut = line.getFieldU32(ripple::sfLowQualityOut); - auto highQualityIn = line.getFieldU32(ripple::sfHighQualityIn); - auto highQualityOut = line.getFieldU32(ripple::sfHighQualityOut); - auto balance = line.getFieldAmount(ripple::sfBalance); + auto const flags = lineSle.getFieldU32(ripple::sfFlags); + auto const lowLimit = lineSle.getFieldAmount(ripple::sfLowLimit); + auto const highLimit = lineSle.getFieldAmount(ripple::sfHighLimit); + auto const lowID = lowLimit.getIssuer(); + auto const highID = highLimit.getIssuer(); + auto const lowQualityIn = lineSle.getFieldU32(ripple::sfLowQualityIn); + auto const lowQualityOut = lineSle.getFieldU32(ripple::sfLowQualityOut); + auto const highQualityIn = lineSle.getFieldU32(ripple::sfHighQualityIn); + auto const highQualityOut = lineSle.getFieldU32(ripple::sfHighQualityOut); + auto balance = lineSle.getFieldAmount(ripple::sfBalance); - bool viewLowest = (lowID == account); - auto lineLimit = viewLowest ? lowLimit : highLimit; - auto lineLimitPeer = !viewLowest ? lowLimit : highLimit; - auto lineAccountIDPeer = !viewLowest ? lowID : highID; - auto lineQualityIn = viewLowest ? lowQualityIn : highQualityIn; - auto lineQualityOut = viewLowest ? lowQualityOut : highQualityOut; + auto const viewLowest = (lowID == account); + auto const lineLimit = viewLowest ? lowLimit : highLimit; + auto const lineLimitPeer = not viewLowest ? lowLimit : highLimit; + auto const lineAccountIDPeer = not viewLowest ? lowID : highID; + auto const lineQualityIn = viewLowest ? lowQualityIn : highQualityIn; + auto const lineQualityOut = viewLowest ? lowQualityOut : highQualityOut; if (peerAccount && peerAccount != lineAccountIDPeer) return; - if (!viewLowest) + if (not viewLowest) balance.negate(); - bool lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); - bool lineAuthPeer = flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); - bool lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); - bool lineNoRipplePeer = flags & (!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); - bool lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - bool lineFreezePeer = flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); + bool const lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); + bool const lineAuthPeer = flags & (not viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); + bool const lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); + bool const lineNoRipplePeer = flags & (not viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); + bool const lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); + bool const lineFreezePeer = flags & (not viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - ripple::STAmount const& saBalance(balance); - ripple::STAmount const& saLimit(lineLimit); - ripple::STAmount const& saLimitPeer(lineLimitPeer); + ripple::STAmount const& saBalance = balance; + ripple::STAmount const& saLimit = lineLimit; + ripple::STAmount const& saLimitPeer = lineLimitPeer; + + LineResponse line; + line.account = ripple::to_string(lineAccountIDPeer); + line.balance = saBalance.getText(); + line.currency = ripple::to_string(saBalance.issue().currency); + line.limit = saLimit.getText(); + line.limitPeer = saLimitPeer.getText(); + line.qualityIn = lineQualityIn; + line.qualityOut = lineQualityOut; - boost::json::object jPeer; - jPeer[JS(account)] = ripple::to_string(lineAccountIDPeer); - jPeer[JS(balance)] = saBalance.getText(); - jPeer[JS(currency)] = ripple::to_string(saBalance.issue().currency); - jPeer[JS(limit)] = saLimit.getText(); - jPeer[JS(limit_peer)] = saLimitPeer.getText(); - jPeer[JS(quality_in)] = lineQualityIn; - jPeer[JS(quality_out)] = lineQualityOut; if (lineAuth) - jPeer[JS(authorized)] = true; - if (lineAuthPeer) - jPeer[JS(peer_authorized)] = true; - if (lineFreeze) - jPeer[JS(freeze)] = true; - if (lineFreezePeer) - jPeer[JS(freeze_peer)] = true; - jPeer[JS(no_ripple)] = lineNoRipple; - jPeer[JS(no_ripple_peer)] = lineNoRipplePeer; + line.authorized = true; - jsonLines.push_back(jPeer); + if (lineAuthPeer) + line.peerAuthorized = true; + + if (lineFreeze) + line.freeze = true; + + if (lineFreezePeer) + line.freezePeer = true; + + line.noRipple = lineNoRipple; + line.noRipplePeer = lineNoRipplePeer; + lines.push_back(line); } -Result -doAccountLines(Context const& context) +AccountLinesHandler::Result +AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + if (not accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - auto rawAcct = - context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); + auto const peerAccountID = input.peer ? accountFromStringStrict(*(input.peer)) : std::optional{}; - if (!rawAcct) - return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; + Output response; + response.lines.reserve(input.limit); - std::optional peerAccount; - if (auto const status = getOptionalAccount(request, peerAccount, JS(peer)); status) - return status; - - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - std::optional marker = {}; - if (request.contains(JS(marker))) - { - if (not request.at(JS(marker)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - - marker = request.at(JS(marker)).as_string().c_str(); - } - - auto ignoreDefault = false; - if (request.contains(JS(ignore_default))) - { - if (not request.at(JS(ignore_default)).is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "ignoreDefaultNotBool"}; - - ignoreDefault = request.at(JS(ignore_default)).as_bool(); - } - - response[JS(account)] = ripple::to_string(accountID); - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - response[JS(limit)] = limit; - response[JS(lines)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonLines = response.at(JS(lines)).as_array(); - - auto const addToResponse = [&](ripple::SLE&& sle) -> void { + auto const addToResponse = [&](ripple::SLE&& sle) { if (sle.getType() == ripple::ltRIPPLE_STATE) { auto ignore = false; - if (ignoreDefault) + if (input.ignoreDefault) { if (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() == accountID) ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfLowReserve); @@ -164,23 +125,109 @@ doAccountLines(Context const& context) ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfHighReserve); } - if (!ignore) - addLine(jsonLines, sle, accountID, peerAccount); + if (not ignore) + addLine(response.lines, sle, *accountID, peerAccountID); } }; - auto next = - traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); + auto const next = ngTraverseOwnedNodes( + *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); + auto const nextMarker = std::get(next); - if (auto status = std::get_if(&next)) - return *status; - - auto nextMarker = std::get(next); + response.account = input.account; + response.limit = input.limit; // not documented, + // https://github.com/XRPLF/xrpl-dev-portal/issues/1838 + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; if (nextMarker.isNonZero()) - response[JS(marker)] = nextMarker.toString(); + response.marker = nextMarker.toString(); return response; } +AccountLinesHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountLinesHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jv.at(JS(account)).as_string().c_str(); + if (jsonObject.contains(JS(limit))) + input.limit = jv.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = jv.at(JS(marker)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(peer))) + input.peer = jv.at(JS(peer)).as_string().c_str(); + + if (jsonObject.contains(JS(ignore_default))) + input.ignoreDefault = jv.at(JS(ignore_default)).as_bool(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountLinesHandler::Output const& output) +{ + auto obj = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(limit), output.limit}, + {JS(lines), output.lines}, + }; + + if (output.marker) + obj[JS(marker)] = output.marker.value(); + + jv = std::move(obj); +} + +void +tag_invoke( + boost::json::value_from_tag, + boost::json::value& jv, + [[maybe_unused]] AccountLinesHandler::LineResponse const& line) +{ + auto obj = boost::json::object{ + {JS(account), line.account}, + {JS(balance), line.balance}, + {JS(currency), line.currency}, + {JS(limit), line.limit}, + {JS(limit_peer), line.limitPeer}, + {JS(quality_in), line.qualityIn}, + {JS(quality_out), line.qualityOut}, + }; + + obj[JS(no_ripple)] = line.noRipple; + obj[JS(no_ripple_peer)] = line.noRipplePeer; + + if (line.authorized) + obj[JS(authorized)] = *(line.authorized); + + if (line.peerAuthorized) + obj[JS(peer_authorized)] = *(line.peerAuthorized); + + if (line.freeze) + obj[JS(freeze)] = *(line.freeze); + + if (line.freezePeer) + obj[JS(freeze_peer)] = *(line.freezePeer); + + jv = std::move(obj); +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountLines.h b/src/rpc/handlers/AccountLines.h similarity index 97% rename from src/rpc/ngHandlers/AccountLines.h rename to src/rpc/handlers/AccountLines.h index a5149bfb..936d3204 100644 --- a/src/rpc/ngHandlers/AccountLines.h +++ b/src/rpc/handlers/AccountLines.h @@ -26,7 +26,7 @@ #include -namespace RPCng { +namespace RPC { class AccountLinesHandler { @@ -74,7 +74,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountLinesHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -117,4 +117,5 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LineResponse const& line); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/ngHandlers/AccountNFTs.cpp b/src/rpc/handlers/AccountNFTs.cpp similarity index 88% rename from src/rpc/ngHandlers/AccountNFTs.cpp rename to src/rpc/handlers/AccountNFTs.cpp index 5c92c520..dd317c6b 100644 --- a/src/rpc/ngHandlers/AccountNFTs.cpp +++ b/src/rpc/handlers/AccountNFTs.cpp @@ -17,47 +17,46 @@ */ //============================================================================== -#include +#include #include -namespace RPCng { +namespace RPC { AccountNFTsHandler::Result AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) const { auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - if (auto const status = std::get_if(&lgrInfoOrStatus)) + if (auto const status = std::get_if(&lgrInfoOrStatus)) return Error{*status}; auto const lgrInfo = std::get(lgrInfoOrStatus); - - auto const accountID = RPC::accountFromStringStrict(input.account); + auto const accountID = accountFromStringStrict(input.account); auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - Output response; + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; + + auto response = Output{}; response.account = input.account; response.limit = input.limit; response.ledgerHash = ripple::strHex(lgrInfo.hash); response.ledgerIndex = lgrInfo.seq; - // if a marker was passed, start at the page specified in marker. Else, - // start at the max page + // if a marker was passed, start at the page specified in marker. Else, start at the max page auto const pageKey = input.marker ? ripple::uint256{input.marker->c_str()} : ripple::keylet::nftpage_max(*accountID).key; - auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield); + if (!blob) return response; std::optional page{ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}}; - auto numPages = 0; + auto numPages = 0u; while (page) { @@ -67,7 +66,7 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) { auto const nftokenID = nft[ripple::sfNFTokenID]; - response.nfts.push_back(RPC::toBoostJson(nft.getJson(ripple::JsonOptions::none))); + response.nfts.push_back(toBoostJson(nft.getJson(ripple::JsonOptions::none))); auto& obj = response.nfts.back().as_object(); // Pull out the components of the nft ID. @@ -89,8 +88,8 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) response.marker = to_string(nextKey.key); return response; } - auto const nextBlob = sharedPtrBackend_->fetchLedgerObject(nextKey.key, lgrInfo.seq, ctx.yield); + auto const nextBlob = sharedPtrBackend_->fetchLedgerObject(nextKey.key, lgrInfo.seq, ctx.yield); page.emplace(ripple::SLE{ripple::SerialIter{nextBlob->data(), nextBlob->size()}, nextKey.key}); } else @@ -98,6 +97,7 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) page.reset(); } } + return response; } @@ -110,7 +110,9 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountNFTsHandl {JS(validated), output.validated}, {JS(account), output.account}, {JS(account_nfts), output.nfts}, - {JS(limit), output.limit}}; + {JS(limit), output.limit}, + }; + if (output.marker) jv.as_object()[JS(marker)] = *output.marker; } @@ -118,9 +120,11 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountNFTsHandl AccountNFTsHandler::Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) { + auto input = AccountNFTsHandler::Input{}; auto const& jsonObject = jv.as_object(); - AccountNFTsHandler::Input input; + input.account = jsonObject.at(JS(account)).as_string().c_str(); + if (jsonObject.contains(JS(ledger_hash))) input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); @@ -131,6 +135,7 @@ tag_invoke(boost::json::value_to_tag, boost::json::va else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); } + if (jsonObject.contains(JS(limit))) input.limit = jsonObject.at(JS(limit)).as_int64(); @@ -140,4 +145,4 @@ tag_invoke(boost::json::value_to_tag, boost::json::va return input; } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/AccountNFTs.h b/src/rpc/handlers/AccountNFTs.h similarity index 96% rename from src/rpc/ngHandlers/AccountNFTs.h rename to src/rpc/handlers/AccountNFTs.h index a674669c..1198ccff 100644 --- a/src/rpc/ngHandlers/AccountNFTs.h +++ b/src/rpc/handlers/AccountNFTs.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class AccountNFTsHandler { std::shared_ptr sharedPtrBackend_; @@ -51,7 +51,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountNFTsHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -81,4 +81,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 00c1057d..da1316af 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,25 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include namespace RPC { -std::unordered_map types{ +// document does not mention nft_page, we still support it tho +std::unordered_map const AccountObjectsHandler::TYPESMAP{ {"state", ripple::ltRIPPLE_STATE}, {"ticket", ripple::ltTICKET}, {"signer_list", ripple::ltSIGNER_LIST}, @@ -45,170 +32,102 @@ std::unordered_map types{ {"deposit_preauth", ripple::ltDEPOSIT_PREAUTH}, {"check", ripple::ltCHECK}, {"nft_page", ripple::ltNFTOKEN_PAGE}, - {"nft_offer", ripple::ltNFTOKEN_OFFER}}; + {"nft_offer", ripple::ltNFTOKEN_OFFER}, +}; -Result -doAccountNFTs(Context const& context) +AccountObjectsHandler::Result +AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - if (!accountID) - return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"}; + Output response; + auto const addToResponse = [&](ripple::SLE&& sle) { + if (!input.type || sle.getType() == *(input.type)) + response.accountObjects.push_back(std::move(sle)); + return true; + }; - auto rawAcct = - context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); + auto const next = ngTraverseOwnedNodes( + *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - if (!rawAcct) - return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; + if (auto status = std::get_if(&next)) + return Error{*status}; - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; + response.limit = input.limit; + response.account = input.account; - ripple::uint256 marker; - if (auto const status = getHexMarker(request, marker); status) - return status; + auto const& nextMarker = std::get(next); - response[JS(account)] = ripple::toBase58(accountID); - response[JS(validated)] = true; - response[JS(limit)] = limit; - - std::uint32_t numPages = 0; - response[JS(account_nfts)] = boost::json::value(boost::json::array_kind); - auto& nfts = response.at(JS(account_nfts)).as_array(); - - // if a marker was passed, start at the page specified in marker. Else, - // start at the max page - auto const pageKey = marker.isZero() ? ripple::keylet::nftpage_max(accountID).key : marker; - - auto const blob = context.backend->fetchLedgerObject(pageKey, lgrInfo.seq, context.yield); - if (!blob) - return response; - std::optional page{ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}}; - - // Continue iteration from the current page - while (page) - { - auto arr = page->getFieldArray(ripple::sfNFTokens); - - for (auto const& o : arr) - { - ripple::uint256 const nftokenID = o[ripple::sfNFTokenID]; - - { - nfts.push_back(toBoostJson(o.getJson(ripple::JsonOptions::none))); - auto& obj = nfts.back().as_object(); - - // Pull out the components of the nft ID. - obj[SFS(sfFlags)] = ripple::nft::getFlags(nftokenID); - obj[SFS(sfIssuer)] = to_string(ripple::nft::getIssuer(nftokenID)); - obj[SFS(sfNFTokenTaxon)] = ripple::nft::toUInt32(ripple::nft::getTaxon(nftokenID)); - obj[JS(nft_serial)] = ripple::nft::getSerial(nftokenID); - - if (std::uint16_t xferFee = {ripple::nft::getTransferFee(nftokenID)}) - obj[SFS(sfTransferFee)] = xferFee; - } - } - - ++numPages; - if (auto npm = (*page)[~ripple::sfPreviousPageMin]) - { - auto const nextKey = ripple::Keylet(ripple::ltNFTOKEN_PAGE, *npm); - if (numPages == limit) - { - response[JS(marker)] = to_string(nextKey.key); - response[JS(limit)] = numPages; - return response; - } - auto const nextBlob = context.backend->fetchLedgerObject(nextKey.key, lgrInfo.seq, context.yield); - - page.emplace(ripple::SLE{ripple::SerialIter{nextBlob->data(), nextBlob->size()}, nextKey.key}); - } - else - page.reset(); - } + if (nextMarker.isNonZero()) + response.marker = nextMarker.toString(); return response; } -Result -doAccountObjects(Context const& context) +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHandler::Output const& output) { - auto request = context.params; - boost::json::object response = {}; + auto objects = boost::json::array{}; + for (auto const& sle : output.accountObjects) + objects.push_back(toJson(sle)); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; - - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - std::optional marker = {}; - if (request.contains("marker")) - { - if (!request.at("marker").is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - - marker = request.at("marker").as_string().c_str(); - } - - std::optional objectType = {}; - if (request.contains(JS(type))) - { - if (!request.at(JS(type)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "typeNotString"}; - - std::string typeAsString = request.at(JS(type)).as_string().c_str(); - if (types.find(typeAsString) == types.end()) - return Status{RippledError::rpcINVALID_PARAMS, "typeInvalid"}; - - objectType = types[typeAsString]; - } - - response[JS(account)] = ripple::to_string(accountID); - response[JS(account_objects)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonObjects = response.at(JS(account_objects)).as_array(); - - auto const addToResponse = [&](ripple::SLE&& sle) { - if (!objectType || objectType == sle.getType()) - { - jsonObjects.push_back(toJson(sle)); - } + jv = { + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(limit), output.limit}, + {JS(account), output.account}, + {JS(account_objects), objects}, }; - auto next = - traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); + if (output.marker) + jv.as_object()[JS(marker)] = *(output.marker); +} - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; +AccountObjectsHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountObjectsHandler::Input{}; + auto const& jsonObject = jv.as_object(); - if (auto status = std::get_if(&next)) - return *status; + input.account = jv.at(JS(account)).as_string().c_str(); - auto const& nextMarker = std::get(next); - if (nextMarker.isNonZero()) - response[JS(marker)] = nextMarker.toString(); + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - return response; + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(type))) + input.type = AccountObjectsHandler::TYPESMAP.at(jv.at(JS(type)).as_string().c_str()); + + if (jsonObject.contains(JS(limit))) + input.limit = jv.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = jv.at(JS(marker)).as_string().c_str(); + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountObjects.h b/src/rpc/handlers/AccountObjects.h similarity index 94% rename from src/rpc/ngHandlers/AccountObjects.h rename to src/rpc/handlers/AccountObjects.h index 0f6b1e11..9b2e66ce 100644 --- a/src/rpc/ngHandlers/AccountObjects.h +++ b/src/rpc/handlers/AccountObjects.h @@ -26,11 +26,13 @@ #include -namespace RPCng { +namespace RPC { class AccountObjectsHandler { // dependencies std::shared_ptr sharedPtrBackend_; + + // constants static std::unordered_map const TYPESMAP; public: @@ -51,12 +53,12 @@ public: std::string account; std::optional ledgerHash; std::optional ledgerIndex; - uint32_t limit = 200; //[10,400] + uint32_t limit = 200; // [10,400] std::optional marker; std::optional type; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountObjectsHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -83,7 +85,8 @@ public: "deposit_preauth", "check", "nft_page", - "nft_offer"}}, + "nft_offer", + }}, {JS(marker), validation::AccountMarkerValidator}, }; @@ -100,4 +103,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index 57c3f83f..2a9bbb6d 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,133 +17,144 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include namespace RPC { void -addOffer(boost::json::array& offersJson, ripple::SLE const& offer) +AccountOffersHandler::addOffer(std::vector& offers, ripple::SLE const& offerSle) const { - auto quality = getQuality(offer.getFieldH256(ripple::sfBookDirectory)); - ripple::STAmount rate = ripple::amountFromQuality(quality); + auto offer = AccountOffersHandler::Offer(); + offer.takerPays = offerSle.getFieldAmount(ripple::sfTakerPays); + offer.takerGets = offerSle.getFieldAmount(ripple::sfTakerGets); + offer.seq = offerSle.getFieldU32(ripple::sfSequence); + offer.flags = offerSle.getFieldU32(ripple::sfFlags); - ripple::STAmount takerPays = offer.getFieldAmount(ripple::sfTakerPays); - ripple::STAmount takerGets = offer.getFieldAmount(ripple::sfTakerGets); + auto const quality = getQuality(offerSle.getFieldH256(ripple::sfBookDirectory)); + auto const rate = ripple::amountFromQuality(quality); + offer.quality = rate.getText(); - boost::json::object obj; + if (offerSle.isFieldPresent(ripple::sfExpiration)) + offer.expiration = offerSle.getFieldU32(ripple::sfExpiration); - if (!takerPays.native()) - { - obj[JS(taker_pays)] = boost::json::value(boost::json::object_kind); - boost::json::object& takerPaysJson = obj.at(JS(taker_pays)).as_object(); - - takerPaysJson[JS(value)] = takerPays.getText(); - takerPaysJson[JS(currency)] = ripple::to_string(takerPays.getCurrency()); - takerPaysJson[JS(issuer)] = ripple::to_string(takerPays.getIssuer()); - } - else - { - obj[JS(taker_pays)] = takerPays.getText(); - } - - if (!takerGets.native()) - { - obj[JS(taker_gets)] = boost::json::value(boost::json::object_kind); - boost::json::object& takerGetsJson = obj.at(JS(taker_gets)).as_object(); - - takerGetsJson[JS(value)] = takerGets.getText(); - takerGetsJson[JS(currency)] = ripple::to_string(takerGets.getCurrency()); - takerGetsJson[JS(issuer)] = ripple::to_string(takerGets.getIssuer()); - } - else - { - obj[JS(taker_gets)] = takerGets.getText(); - } - - obj[JS(seq)] = offer.getFieldU32(ripple::sfSequence); - obj[JS(flags)] = offer.getFieldU32(ripple::sfFlags); - obj[JS(quality)] = rate.getText(); - if (offer.isFieldPresent(ripple::sfExpiration)) - obj[JS(expiration)] = offer.getFieldU32(ripple::sfExpiration); - - offersJson.push_back(obj); + offers.push_back(offer); }; -Result -doAccountOffers(Context const& context) +AccountOffersHandler::Result +AccountOffersHandler::process(AccountOffersHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto lgrInfo = std::get(v); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - auto rawAcct = - context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); - - if (!rawAcct) - return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; - - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - std::optional marker = {}; - if (request.contains(JS(marker))) - { - if (!request.at(JS(marker)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - - marker = request.at(JS(marker)).as_string().c_str(); - } - - response[JS(account)] = ripple::to_string(accountID); - response[JS(limit)] = limit; - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - response[JS(offers)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonLines = response.at(JS(offers)).as_array(); + Output response; + response.account = ripple::to_string(*accountID); + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; auto const addToResponse = [&](ripple::SLE&& sle) { if (sle.getType() == ripple::ltOFFER) - { - addOffer(jsonLines, sle); - } + addOffer(response.offers, sle); return true; }; - auto next = - traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); + auto const next = ngTraverseOwnedNodes( + *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - if (auto status = std::get_if(&next)) - return *status; + if (auto const status = std::get_if(&next)) + return Error{*status}; - auto nextMarker = std::get(next); + auto const nextMarker = std::get(next); if (nextMarker.isNonZero()) - response[JS(marker)] = nextMarker.toString(); + response.marker = nextMarker.toString(); return response; } +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Output const& output) +{ + jv = { + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(account), output.account}, + {JS(offers), boost::json::value_from(output.offers)}, + }; + + if (output.marker) + jv.as_object()[JS(marker)] = *output.marker; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Offer const& offer) +{ + jv = { + {JS(seq), offer.seq}, + {JS(flags), offer.flags}, + {JS(quality), offer.quality}, + }; + + auto& jsonObject = jv.as_object(); + + if (offer.expiration) + jsonObject[JS(expiration)] = *offer.expiration; + + auto const convertAmount = [&](const char* field, ripple::STAmount const& amount) { + if (amount.native()) + jsonObject[field] = amount.getText(); + else + jsonObject[field] = { + {JS(currency), ripple::to_string(amount.getCurrency())}, + {JS(issuer), ripple::to_string(amount.getIssuer())}, + {JS(value), amount.getText()}, + }; + }; + + convertAmount(JS(taker_pays), offer.takerPays); + convertAmount(JS(taker_gets), offer.takerGets); +} + +AccountOffersHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountOffersHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jsonObject.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + { + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + } + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = jsonObject.at(JS(marker)).as_string().c_str(); + + return input; +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountOffers.h b/src/rpc/handlers/AccountOffers.h similarity index 95% rename from src/rpc/ngHandlers/AccountOffers.h rename to src/rpc/handlers/AccountOffers.h index be6fa6cc..eb5a9500 100644 --- a/src/rpc/ngHandlers/AccountOffers.h +++ b/src/rpc/handlers/AccountOffers.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class AccountOffersHandler { std::shared_ptr sharedPtrBackend_; @@ -51,7 +51,7 @@ public: bool validated = true; }; - // TODO:we did not implement the "strict" field + // TODO: We did not implement the "strict" field struct Input { std::string account; @@ -61,7 +61,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountOffersHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -98,4 +98,4 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Offer const& offer); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/AccountTx.cpp b/src/rpc/handlers/AccountTx.cpp index b8e0168a..006f6d33 100644 --- a/src/rpc/handlers/AccountTx.cpp +++ b/src/rpc/handlers/AccountTx.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,48 +17,196 @@ */ //============================================================================== -#include -#include +#include #include -using namespace clio; - -// local to compilation unit loggers -namespace { -clio::Logger gLog{"RPC"}; -} // namespace - namespace RPC { -Result -doAccountTx(Context const& context) +// TODO: this is currently very similar to nft_history but its own copy for time +// being. we should aim to reuse common logic in some way in the future. +AccountTxHandler::Result +AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) const { - ripple::AccountID accountID; - if (auto const status = getAccount(context.params, accountID); status) - return status; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto [minIndex, maxIndex] = *range; - constexpr std::string_view outerFuncName = __func__; - auto const maybeResponse = traverseTransactions( - context, - [&accountID, &outerFuncName]( - std::shared_ptr const& backend, - std::uint32_t const limit, - bool const forward, - std::optional const& cursorIn, - boost::asio::yield_context& yield) { - auto [txnsAndCursor, timeDiff] = util::timed( - [&]() { return backend->fetchAccountTransactions(accountID, limit, forward, cursorIn, yield); }); - gLog.info() << outerFuncName << " db fetch took " << timeDiff - << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); - return txnsAndCursor; - }); + if (input.ledgerIndexMin) + { + if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin) + return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}}; - if (auto const status = std::get_if(&maybeResponse); status) - return *status; - auto response = std::get(maybeResponse); + minIndex = *input.ledgerIndexMin; + } + + if (input.ledgerIndexMax) + { + if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax) + return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}}; + + maxIndex = *input.ledgerIndexMax; + } + + if (minIndex > maxIndex) + return Error{Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"}}; + + if (input.ledgerHash || input.ledgerIndex) + { + // rippled does not have this check + if (input.ledgerIndexMax || input.ledgerIndexMin) + return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}}; + + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); + + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; + + maxIndex = minIndex = std::get(lgrInfoOrStatus).seq; + } + + std::optional cursor; + + // if marker exists + if (input.marker) + { + cursor = {input.marker->ledger, input.marker->seq}; + } + else + { + if (input.forward) + cursor = {minIndex, 0}; + else + cursor = {maxIndex, INT32_MAX}; + } + + static auto constexpr limitDefault = 50; + auto const limit = input.limit.value_or(limitDefault); + auto const accountID = accountFromStringStrict(input.account); + auto const [txnsAndCursor, timeDiff] = util::timed([&]() { + return sharedPtrBackend_->fetchAccountTransactions(*accountID, limit, input.forward, cursor, ctx.yield); + }); + + log_.info() << "db fetch took " << timeDiff << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); + + auto const [blobs, retCursor] = txnsAndCursor; + Output response; + + if (retCursor) + response.marker = {retCursor->ledgerSequence, retCursor->transactionIndex}; + + for (auto const& txnPlusMeta : blobs) + { + // over the range + if ((txnPlusMeta.ledgerSequence < minIndex && !input.forward) || + (txnPlusMeta.ledgerSequence > maxIndex && input.forward)) + { + response.marker = std::nullopt; + break; + } + else if (txnPlusMeta.ledgerSequence > maxIndex && !input.forward) + { + log_.debug() << "Skipping over transactions from incomplete ledger"; + continue; + } + + boost::json::object obj; + if (!input.binary) + { + auto [txn, meta] = toExpandedJson(txnPlusMeta); + obj[JS(meta)] = std::move(meta); + obj[JS(tx)] = std::move(txn); + obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence; + obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; + } + else + { + obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); + obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); + obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; + // only clio has this field + obj[JS(date)] = txnPlusMeta.date; + } + + obj[JS(validated)] = true; + + response.transactions.push_back(obj); + } + + response.limit = input.limit; + response.account = ripple::to_string(*accountID); + response.ledgerIndexMin = minIndex; + response.ledgerIndexMax = maxIndex; - response[JS(account)] = ripple::to_string(accountID); return response; } +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountTxHandler::Output const& output) +{ + jv = { + {JS(account), output.account}, + {JS(ledger_index_min), output.ledgerIndexMin}, + {JS(ledger_index_max), output.ledgerIndexMax}, + {JS(transactions), output.transactions}, + {JS(validated), output.validated}, + }; + + if (output.marker) + jv.as_object()[JS(marker)] = boost::json::value_from(*(output.marker)); + + if (output.limit) + jv.as_object()[JS(limit)] = *(output.limit); +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountTxHandler::Marker const& marker) +{ + jv = { + {JS(ledger), marker.ledger}, + {JS(seq), marker.seq}, + }; +} + +AccountTxHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = AccountTxHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jsonObject.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index_min)) && jsonObject.at(JS(ledger_index_min)).as_int64() != -1) + input.ledgerIndexMin = jsonObject.at(JS(ledger_index_min)).as_int64(); + + if (jsonObject.contains(JS(ledger_index_max)) && jsonObject.at(JS(ledger_index_max)).as_int64() != -1) + input.ledgerIndexMax = jsonObject.at(JS(ledger_index_max)).as_int64(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(binary))) + input.binary = jsonObject.at(JS(binary)).as_bool(); + + if (jsonObject.contains(JS(forward))) + input.forward = jsonObject.at(JS(forward)).as_bool(); + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = AccountTxHandler::Marker{ + jsonObject.at(JS(marker)).as_object().at(JS(ledger)).as_int64(), + jsonObject.at(JS(marker)).as_object().at(JS(seq)).as_int64()}; + + return input; +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/AccountTx.h b/src/rpc/handlers/AccountTx.h similarity index 95% rename from src/rpc/ngHandlers/AccountTx.h rename to src/rpc/handlers/AccountTx.h index 1f624f03..f89797f5 100644 --- a/src/rpc/ngHandlers/AccountTx.h +++ b/src/rpc/handlers/AccountTx.h @@ -25,7 +25,7 @@ #include #include -namespace RPCng { +namespace RPC { class AccountTxHandler { clio::Logger log_{"RPC"}; @@ -67,7 +67,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; AccountTxHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -88,7 +88,8 @@ public: {JS(marker), validation::WithCustomError{ validation::Type{}, - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidMarker"}}, + Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}, + }, validation::Section{ {JS(ledger), validation::Required{}, validation::Type{}}, {JS(seq), validation::Required{}, validation::Type{}}, @@ -111,4 +112,4 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/BookChanges.cpp b/src/rpc/handlers/BookChanges.cpp index 877628a4..a9757404 100644 --- a/src/rpc/handlers/BookChanges.cpp +++ b/src/rpc/handlers/BookChanges.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,243 +17,79 @@ */ //============================================================================== -#include -#include - -#include -#include - -#include -#include - -namespace json = boost::json; -using namespace ripple; +#include namespace RPC { -/** - * @brief Represents an entry in the book_changes' changes array. - */ -struct BookChange +BookChangesHandler::Result +BookChangesHandler::process(BookChangesHandler::Input input, Context const& ctx) const { - STAmount sideAVolume; - STAmount sideBVolume; - STAmount highRate; - STAmount lowRate; - STAmount openRate; - STAmount closeRate; -}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); -/** - * @brief Encapsulates the book_changes computations and transformations. - */ -class BookChanges final -{ -public: - BookChanges() = delete; // only accessed via static handle function + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - /** - * @brief Computes all book_changes for the given transactions. - * - * @param transactions The transactions to compute book changes for - * @return std::vector Book changes - */ - [[nodiscard]] static std::vector - compute(std::vector const& transactions) - { - return HandlerImpl{}(transactions); - } + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const transactions = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield); -private: - class HandlerImpl final - { - std::map tally_ = {}; - std::optional offerCancel_ = {}; + Output response; + response.bookChanges = BookChanges::compute(transactions); + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; + response.ledgerTime = lgrInfo.closeTime.time_since_epoch().count(); - public: - [[nodiscard]] std::vector - operator()(std::vector const& transactions) - { - for (auto const& tx : transactions) - handleBookChange(tx); - - // TODO: rewrite this with std::ranges when compilers catch up - std::vector changes; - std::transform( - std::make_move_iterator(std::begin(tally_)), - std::make_move_iterator(std::end(tally_)), - std::back_inserter(changes), - [](auto obj) { return obj.second; }); - return changes; - } - - private: - void - handleAffectedNode(STObject const& node) - { - auto const& metaType = node.getFName(); - auto const nodeType = node.getFieldU16(sfLedgerEntryType); - - // we only care about ltOFFER objects being modified or - // deleted - if (nodeType != ltOFFER || metaType == sfCreatedNode) - return; - - // if either FF or PF are missing we can't compute - // but generally these are cancelled rather than crossed - // so skipping them is consistent - if (!node.isFieldPresent(sfFinalFields) || !node.isFieldPresent(sfPreviousFields)) - return; - - auto const& finalFields = node.peekAtField(sfFinalFields).downcast(); - auto const& previousFields = node.peekAtField(sfPreviousFields).downcast(); - - // defensive case that should never be hit - if (!finalFields.isFieldPresent(sfTakerGets) || !finalFields.isFieldPresent(sfTakerPays) || - !previousFields.isFieldPresent(sfTakerGets) || !previousFields.isFieldPresent(sfTakerPays)) - return; - - // filter out any offers deleted by explicit offer cancels - if (metaType == sfDeletedNode && offerCancel_ && finalFields.getFieldU32(sfSequence) == *offerCancel_) - return; - - // compute the difference in gets and pays actually - // affected onto the offer - auto const deltaGets = finalFields.getFieldAmount(sfTakerGets) - previousFields.getFieldAmount(sfTakerGets); - auto const deltaPays = finalFields.getFieldAmount(sfTakerPays) - previousFields.getFieldAmount(sfTakerPays); - - transformAndStore(deltaGets, deltaPays); - } - - void - transformAndStore(ripple::STAmount const& deltaGets, ripple::STAmount const& deltaPays) - { - auto const g = to_string(deltaGets.issue()); - auto const p = to_string(deltaPays.issue()); - - auto const noswap = isXRP(deltaGets) ? true : (isXRP(deltaPays) ? false : (g < p)); - - auto first = noswap ? deltaGets : deltaPays; - auto second = noswap ? deltaPays : deltaGets; - - // defensively programmed, should (probably) never happen - if (second == beast::zero) - return; - - auto const rate = divide(first, second, noIssue()); - - if (first < beast::zero) - first = -first; - - if (second < beast::zero) - second = -second; - - auto const key = noswap ? (g + '|' + p) : (p + '|' + g); - if (tally_.contains(key)) - { - auto& entry = tally_.at(key); - - entry.sideAVolume += first; - entry.sideBVolume += second; - - if (entry.highRate < rate) - entry.highRate = rate; - - if (entry.lowRate > rate) - entry.lowRate = rate; - - entry.closeRate = rate; - } - else - { - // TODO: use paranthesized initialization when clang catches up - tally_[key] = { - first, // sideAVolume - second, // sideBVolume - rate, // highRate - rate, // lowRate - rate, // openRate - rate, // closeRate - }; - } - } - - void - handleBookChange(Backend::TransactionAndMetadata const& blob) - { - auto const [tx, meta] = deserializeTxPlusMeta(blob); - if (!tx || !meta || !tx->isFieldPresent(sfTransactionType)) - return; - - offerCancel_ = shouldCancelOffer(tx); - for (auto const& node : meta->getFieldArray(sfAffectedNodes)) - handleAffectedNode(node); - } - - std::optional - shouldCancelOffer(std::shared_ptr const& tx) const - { - switch (tx->getFieldU16(sfTransactionType)) - { - // in future if any other ways emerge to cancel an offer - // this switch makes them easy to add - case ttOFFER_CANCEL: - case ttOFFER_CREATE: - if (tx->isFieldPresent(sfOfferSequence)) - return tx->getFieldU32(sfOfferSequence); - default: - return std::nullopt; - } - } - }; -}; + return response; +} void -tag_invoke(json::value_from_tag, json::value& jv, BookChange const& change) +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChangesHandler::Output const& output) { - auto amountStr = [](STAmount const& amount) -> std::string { - return isXRP(amount) ? to_string(amount.xrp()) : to_string(amount.iou()); - }; - - auto currencyStr = [](STAmount const& amount) -> std::string { - return isXRP(amount) ? "XRP_drops" : to_string(amount.issue()); - }; + using boost::json::value_from; jv = { - {JS(currency_a), currencyStr(change.sideAVolume)}, - {JS(currency_b), currencyStr(change.sideBVolume)}, - {JS(volume_a), amountStr(change.sideAVolume)}, - {JS(volume_b), amountStr(change.sideBVolume)}, - {JS(high), to_string(change.highRate.iou())}, - {JS(low), to_string(change.lowRate.iou())}, - {JS(open), to_string(change.openRate.iou())}, - {JS(close), to_string(change.closeRate.iou())}, + {JS(type), "bookChanges"}, + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(ledger_time), output.ledgerTime}, + {JS(validated), output.validated}, + {JS(changes), value_from(output.bookChanges)}, }; } -json::object const +BookChangesHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = BookChangesHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + +[[nodiscard]] boost::json::object const computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector const& transactions) { + using boost::json::value_from; + return { {JS(type), "bookChanges"}, {JS(ledger_index), lgrInfo.seq}, {JS(ledger_hash), to_string(lgrInfo.hash)}, {JS(ledger_time), lgrInfo.closeTime.time_since_epoch().count()}, - {JS(changes), json::value_from(BookChanges::compute(transactions))}, + {JS(changes), value_from(BookChanges::compute(transactions))}, }; } -Result -doBookChanges(Context const& context) -{ - auto const request = context.params; - auto const info = ledgerInfoFromRequest(context); - if (auto const status = std::get_if(&info)) - return *status; - - auto const lgrInfo = std::get(info); - auto const transactions = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq, context.yield); - return computeBookChanges(lgrInfo, transactions); -} - } // namespace RPC diff --git a/src/rpc/ngHandlers/BookChanges.h b/src/rpc/handlers/BookChanges.h similarity index 96% rename from src/rpc/ngHandlers/BookChanges.h rename to src/rpc/handlers/BookChanges.h index 49eebe52..4940886c 100644 --- a/src/rpc/ngHandlers/BookChanges.h +++ b/src/rpc/handlers/BookChanges.h @@ -25,7 +25,7 @@ #include #include -namespace RPCng { +namespace RPC { class BookChangesHandler { @@ -48,7 +48,7 @@ public: std::optional ledgerIndex; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; BookChangesHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -61,6 +61,7 @@ public: {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_index), validation::LedgerIndexValidator}, }; + return rpcSpec; } @@ -74,4 +75,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/BookOffers.cpp b/src/rpc/handlers/BookOffers.cpp index 7ab0caa6..373f8cbf 100644 --- a/src/rpc/handlers/BookOffers.cpp +++ b/src/rpc/handlers/BookOffers.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,90 +17,85 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include - -#include - -#include - -using namespace clio; - -// local to compilation unit loggers -namespace { -clio::Logger gLog{"RPC"}; -} // namespace +#include namespace RPC { -Result -doBookOffers(Context const& context) +BookOffersHandler::Result +BookOffersHandler::process(Input input, Context const& ctx) const { - auto request = context.params; + auto bookMaybe = parseBook(input.paysCurrency, input.paysID, input.getsCurrency, input.getsID); + if (auto const status = std::get_if(&bookMaybe)) + return Error{*status}; - boost::json::object response = {}; - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + // check ledger + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto lgrInfo = std::get(v); + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - ripple::Book book; - ripple::uint256 bookBase; - if (request.contains("book")) + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const book = std::get(bookMaybe); + auto const bookKey = getBookBase(book); + + // TODO: Add perfomance metrics if needed in future + auto [offers, _] = sharedPtrBackend_->fetchBookOffers(bookKey, lgrInfo.seq, input.limit, ctx.yield); + + auto output = BookOffersHandler::Output{}; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + output.ledgerIndex = lgrInfo.seq; + output.offers = postProcessOrderBook( + offers, book, input.taker ? *(input.taker) : beast::zero, *sharedPtrBackend_, lgrInfo.seq, ctx.yield); + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookOffersHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(offers), output.offers}, + }; +} + +BookOffersHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = BookOffersHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + ripple::to_currency(input.getsCurrency, jv.at(JS(taker_gets)).as_object().at(JS(currency)).as_string().c_str()); + ripple::to_currency(input.paysCurrency, jv.at(JS(taker_pays)).as_object().at(JS(currency)).as_string().c_str()); + + if (jv.at(JS(taker_gets)).as_object().contains(JS(issuer))) + ripple::to_issuer(input.getsID, jv.at(JS(taker_gets)).as_object().at(JS(issuer)).as_string().c_str()); + + if (jv.at(JS(taker_pays)).as_object().contains(JS(issuer))) + ripple::to_issuer(input.paysID, jv.at(JS(taker_pays)).as_object().at(JS(issuer)).as_string().c_str()); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) { - if (!request.at("book").is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "bookNotString"}; - - if (!bookBase.parseHex(request.at("book").as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "invalidBook"}; - } - else - { - auto parsed = parseBook(request); - if (auto status = std::get_if(&parsed)) - return *status; - else - { - book = std::get(parsed); - bookBase = getBookBase(book); - } + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); } - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; + if (jsonObject.contains(JS(taker))) + input.taker = accountFromStringStrict(jv.at(JS(taker)).as_string().c_str()); - ripple::AccountID takerID = beast::zero; - if (auto const status = getTaker(request, takerID); status) - return status; + if (jsonObject.contains(JS(limit))) + input.limit = jv.at(JS(limit)).as_int64(); - auto start = std::chrono::system_clock::now(); - auto [offers, _] = context.backend->fetchBookOffers(bookBase, lgrInfo.seq, limit, context.yield); - auto end = std::chrono::system_clock::now(); - - gLog.warn() << "Time loading books: " << std::chrono::duration_cast(end - start).count() - << " milliseconds - request = " << request; - - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - - response[JS(offers)] = postProcessOrderBook(offers, book, takerID, *context.backend, lgrInfo.seq, context.yield); - - auto end2 = std::chrono::system_clock::now(); - - gLog.warn() << "Time transforming to json: " - << std::chrono::duration_cast(end2 - end).count() - << " milliseconds - request = " << request; - return response; + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/BookOffers.h b/src/rpc/handlers/BookOffers.h similarity index 84% rename from src/rpc/ngHandlers/BookOffers.h rename to src/rpc/handlers/BookOffers.h index 95cfd5d5..1bce6cf9 100644 --- a/src/rpc/ngHandlers/BookOffers.h +++ b/src/rpc/handlers/BookOffers.h @@ -23,7 +23,7 @@ #include #include -namespace RPCng { +namespace RPC { class BookOffersHandler { std::shared_ptr sharedPtrBackend_; @@ -37,8 +37,8 @@ public: bool validated = true; }; - // the taker is not really used in both clio and rippled, both of them - // return all the offers regardless the funding status + // the taker is not really used in both clio and rippled, both of them return all the offers regardless the funding + // status struct Input { std::optional ledgerHash; @@ -47,13 +47,12 @@ public: std::optional taker; ripple::Currency paysCurrency; ripple::Currency getsCurrency; - // accountID will be filled by input converter, if no issuer is given, - // will use XRP issuer + // accountID will be filled by input converter, if no issuer is given, will use XRP issuer ripple::AccountID paysID = ripple::xrpAccount(); ripple::AccountID getsID = ripple::xrpAccount(); }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; BookOffersHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -70,10 +69,10 @@ public: {JS(currency), validation::Required{}, validation::WithCustomError{ - validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcDST_AMT_MALFORMED)}}, + validation::CurrencyValidator, Status(RippledError::rpcDST_AMT_MALFORMED)}}, {JS(issuer), validation::WithCustomError{ - validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcDST_ISR_MALFORMED)}}}}, + validation::IssuerValidator, Status(RippledError::rpcDST_ISR_MALFORMED)}}}}, {JS(taker_pays), validation::Required{}, validation::Type{}, @@ -81,10 +80,10 @@ public: {JS(currency), validation::Required{}, validation::WithCustomError{ - validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcSRC_CUR_MALFORMED)}}, + validation::CurrencyValidator, Status(RippledError::rpcSRC_CUR_MALFORMED)}}, {JS(issuer), validation::WithCustomError{ - validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcSRC_ISR_MALFORMED)}}}}, + validation::IssuerValidator, Status(RippledError::rpcSRC_ISR_MALFORMED)}}}}, {JS(taker), validation::AccountValidator}, {JS(limit), validation::Type{}, validation::Between{1, 100}}, {JS(ledger_hash), validation::Uint256HexStringValidator}, @@ -104,4 +103,4 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/ChannelAuthorize.cpp b/src/rpc/handlers/ChannelAuthorize.cpp deleted file mode 100644 index 20671a8c..00000000 --- a/src/rpc/handlers/ChannelAuthorize.cpp +++ /dev/null @@ -1,88 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPC { - -void -serializePayChanAuthorization(ripple::Serializer& msg, ripple::uint256 const& key, ripple::XRPAmount const& amt) -{ - msg.add32(ripple::HashPrefix::paymentChannelClaim); - msg.addBitString(key); - msg.add64(amt.drops()); -} - -Result -doChannelAuthorize(Context const& context) -{ - auto request = context.params; - boost::json::object response = {}; - - if (!request.contains(JS(amount))) - return Status{RippledError::rpcINVALID_PARAMS, "missingAmount"}; - - if (!request.at(JS(amount)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "amountNotString"}; - - if (!request.contains(JS(key_type)) && !request.contains(JS(secret))) - return Status{RippledError::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"}; - - auto v = keypairFromRequst(request); - if (auto status = std::get_if(&v)) - return *status; - - auto const [pk, sk] = std::get>(v); - - ripple::uint256 channelId; - if (auto const status = getChannelId(request, channelId); status) - return status; - - auto optDrops = ripple::to_uint64(request.at(JS(amount)).as_string().c_str()); - - if (!optDrops) - return Status{RippledError::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; - - std::uint64_t drops = *optDrops; - - ripple::Serializer msg; - ripple::serializePayChanAuthorization(msg, channelId, ripple::XRPAmount(drops)); - - try - { - auto const buf = ripple::sign(pk, sk, msg.slice()); - response[JS(signature)] = ripple::strHex(buf); - } - catch (std::exception&) - { - return Status{RippledError::rpcINTERNAL}; - } - - return response; -} - -} // namespace RPC diff --git a/src/rpc/handlers/ChannelVerify.cpp b/src/rpc/handlers/ChannelVerify.cpp deleted file mode 100644 index d20a0685..00000000 --- a/src/rpc/handlers/ChannelVerify.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPC { - -Result -doChannelVerify(Context const& context) -{ - auto request = context.params; - boost::json::object response = {}; - - if (!request.contains(JS(amount))) - return Status{RippledError::rpcINVALID_PARAMS, "missingAmount"}; - - if (!request.at(JS(amount)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "amountNotString"}; - - if (!request.contains(JS(signature))) - return Status{RippledError::rpcINVALID_PARAMS, "missingSignature"}; - - if (!request.at(JS(signature)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "signatureNotString"}; - - if (!request.contains(JS(public_key))) - return Status{RippledError::rpcINVALID_PARAMS, "missingPublicKey"}; - - if (!request.at(JS(public_key)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "publicKeyNotString"}; - - std::optional pk; - { - std::string const strPk = request.at(JS(public_key)).as_string().c_str(); - pk = ripple::parseBase58(ripple::TokenType::AccountPublic, strPk); - - if (!pk) - { - auto pkHex = ripple::strUnHex(strPk); - if (!pkHex) - return Status{RippledError::rpcPUBLIC_MALFORMED, "malformedPublicKey"}; - - auto const pkType = ripple::publicKeyType(ripple::makeSlice(*pkHex)); - if (!pkType) - return Status{RippledError::rpcPUBLIC_MALFORMED, "invalidKeyType"}; - - pk.emplace(ripple::makeSlice(*pkHex)); - } - } - - ripple::uint256 channelId; - if (auto const status = getChannelId(request, channelId); status) - return status; - - auto optDrops = ripple::to_uint64(request.at(JS(amount)).as_string().c_str()); - - if (!optDrops) - return Status{RippledError::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; - - std::uint64_t drops = *optDrops; - - auto sig = ripple::strUnHex(request.at(JS(signature)).as_string().c_str()); - - if (!sig || !sig->size()) - return Status{RippledError::rpcINVALID_PARAMS, "invalidSignature"}; - - ripple::Serializer msg; - ripple::serializePayChanAuthorization(msg, channelId, ripple::XRPAmount(drops)); - - response[JS(signature_verified)] = ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true); - - return response; -} - -} // namespace RPC diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index 038d791f..70e34daa 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,177 +17,138 @@ */ //============================================================================== -#include -#include +#include namespace RPC { -Result -doGatewayBalances(Context const& context) +GatewayBalancesHandler::Result +GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + // check ledger + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + // check account + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - auto lgrInfo = std::get(v); + if (!accountLedgerObject) + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - std::map sums; - std::map> hotBalances; - std::map> assets; - std::map> frozenBalances; - std::set hotWallets; + auto output = GatewayBalancesHandler::Output{}; - if (request.contains(JS(hotwallet))) - { - auto getAccountID = [](auto const& j) -> std::optional { - if (j.is_string()) - { - auto const pk = - ripple::parseBase58(ripple::TokenType::AccountPublic, j.as_string().c_str()); - if (pk) - { - return ripple::calcAccountID(*pk); - } - - return ripple::parseBase58(j.as_string().c_str()); - } - return {}; - }; - - auto const& hw = request.at(JS(hotwallet)); - bool valid = true; - - // null is treated as a valid 0-sized array of hotwallet - if (hw.is_array()) - { - auto const& arr = hw.as_array(); - for (unsigned i = 0; i < arr.size(); ++i) - { - if (auto id = getAccountID(arr[i])) - hotWallets.insert(*id); - else - valid = false; - } - } - else if (hw.is_string()) - { - if (auto id = getAccountID(hw)) - hotWallets.insert(*id); - else - valid = false; - } - else - { - valid = false; - } - - if (!valid) - { - response[JS(error)] = "invalidHotWallet"; - return response; - } - } - - // Traverse the cold wallet's trust lines auto const addToResponse = [&](ripple::SLE&& sle) { if (sle.getType() == ripple::ltRIPPLE_STATE) { ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); + auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit); + auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit); + auto const lowID = lowLimit.getIssuer(); + auto const highID = highLimit.getIssuer(); + auto const viewLowest = (lowLimit.getIssuer() == accountID); + auto const flags = sle.getFieldU32(ripple::sfFlags); + auto const freeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit); - auto highLimit = sle.getFieldAmount(ripple::sfHighLimit); - auto lowID = lowLimit.getIssuer(); - auto highID = highLimit.getIssuer(); - bool viewLowest = (lowLimit.getIssuer() == accountID); - auto lineLimit = viewLowest ? lowLimit : highLimit; - auto lineLimitPeer = !viewLowest ? lowLimit : highLimit; - auto flags = sle.getFieldU32(ripple::sfFlags); - auto freeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); if (!viewLowest) balance.negate(); - int balSign = balance.signum(); + auto const balSign = balance.signum(); if (balSign == 0) return true; auto const& peer = !viewLowest ? lowID : highID; // Here, a negative balance means the cold wallet owes (normal) - // A positive balance means the cold wallet has an asset - // (unusual) + // A positive balance means the cold wallet has an asset (unusual) - if (hotWallets.count(peer) > 0) + if (input.hotWallets.count(peer) > 0) { // This is a specified hot wallet - hotBalances[peer].push_back(-balance); + output.hotBalances[peer].push_back(-balance); } else if (balSign > 0) { // This is a gateway asset - assets[peer].push_back(balance); + output.assets[peer].push_back(balance); } else if (freeze) { // An obligation the gateway has frozen - frozenBalances[peer].push_back(-balance); + output.frozenBalances[peer].push_back(-balance); } else { // normal negative balance, obligation to customer - auto& bal = sums[balance.getCurrency()]; + auto& bal = output.sums[balance.getCurrency()]; if (bal == beast::zero) { // This is needed to set the currency code correctly bal = -balance; } else - { // when overflow happens, insert a flag to indicate - // https://github.com/XRPLF/rippled/pull/4355 + { try { bal -= balance; } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { - response["overflow"] = true; + output.overflow = true; } } } } + return true; }; - auto result = traverseOwnedNodes( - *context.backend, - accountID, + // traverse all owned nodes, limit->max, marker->empty + auto const ret = ngTraverseOwnedNodes( + *sharedPtrBackend_, + *accountID, lgrInfo.seq, std::numeric_limits::max(), {}, - context.yield, + ctx.yield, addToResponse); - if (auto status = std::get_if(&result)) - return *status; - if (!sums.empty()) + if (auto status = std::get_if(&ret)) + return Error{*status}; + + auto inHotbalances = [&](auto const& hw) { return output.hotBalances.contains(hw); }; + if (not std::all_of(input.hotWallets.begin(), input.hotWallets.end(), inHotbalances)) + return Error{Status{RippledError::rpcINVALID_PARAMS, "invalidHotWallet"}}; + + output.accountID = input.account; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + output.ledgerIndex = lgrInfo.seq; + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesHandler::Output const& output) +{ + boost::json::object obj; + if (!output.sums.empty()) { - boost::json::object obj; - for (auto const& [k, v] : sums) - { - obj[ripple::to_string(k)] = v.getText(); - } - response[JS(obligations)] = std::move(obj); + boost::json::object obligations; + for (auto const& [k, v] : output.sums) + obligations[ripple::to_string(k)] = v.getText(); + + obj[JS(obligations)] = std::move(obligations); } - auto toJson = [](std::map> const& balances) { - boost::json::object obj; - if (!balances.empty()) + auto const toJson = [](std::map> const& balances) { + boost::json::object balancesObj; + + if (not balances.empty()) { for (auto const& [accId, accBalances] : balances) { @@ -199,25 +160,72 @@ doGatewayBalances(Context const& context) entry[JS(value)] = balance.getText(); arr.push_back(std::move(entry)); } - obj[ripple::to_string(accId)] = std::move(arr); + + balancesObj[ripple::to_string(accId)] = std::move(arr); } } - return obj; + + return balancesObj; }; - auto containsHotWallet = [&](auto const& hw) { return hotBalances.contains(hw); }; - if (not std::all_of(hotWallets.begin(), hotWallets.end(), containsHotWallet)) - return Status{RippledError::rpcINVALID_PARAMS, "invalidHotWallet"}; + if (auto balances = toJson(output.hotBalances); balances.size()) + obj[JS(balances)] = balances; - if (auto balances = toJson(hotBalances); balances.size()) - response[JS(balances)] = balances; - if (auto balances = toJson(frozenBalances); balances.size()) - response[JS(frozen_balances)] = balances; - if (auto balances = toJson(assets); assets.size()) - response[JS(assets)] = toJson(assets); - response[JS(account)] = request.at(JS(account)); - response[JS(ledger_index)] = lgrInfo.seq; - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - return response; + // we don't have frozen_balances field in the + // document:https://xrpl.org/gateway_balances.html#gateway_balances + if (auto balances = toJson(output.frozenBalances); balances.size()) + obj[JS(frozen_balances)] = balances; + + if (auto balances = toJson(output.assets); balances.size()) + obj[JS(assets)] = balances; + + obj[JS(account)] = output.accountID; + obj[JS(ledger_index)] = output.ledgerIndex; + obj[JS(ledger_hash)] = output.ledgerHash; + + if (output.overflow) + obj["overflow"] = true; + + jv = std::move(obj); } + +GatewayBalancesHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = GatewayBalancesHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jv.at(JS(account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(hotwallet))) + { + if (jsonObject.at(JS(hotwallet)).is_string()) + { + input.hotWallets.insert(*accountFromStringStrict(jv.at(JS(hotwallet)).as_string().c_str())); + } + else + { + auto const& hotWallets = jv.at(JS(hotwallet)).as_array(); + std::transform( + hotWallets.begin(), + hotWallets.end(), + std::inserter(input.hotWallets, input.hotWallets.begin()), + [](auto const& hotWallet) { return *accountFromStringStrict(hotWallet.as_string().c_str()); }); + } + } + + return input; +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/GatewayBalances.h b/src/rpc/handlers/GatewayBalances.h similarity index 91% rename from src/rpc/ngHandlers/GatewayBalances.h rename to src/rpc/handlers/GatewayBalances.h index 098c4d41..91e1412a 100644 --- a/src/rpc/ngHandlers/GatewayBalances.h +++ b/src/rpc/handlers/GatewayBalances.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class GatewayBalancesHandler { std::shared_ptr sharedPtrBackend_; @@ -53,7 +53,7 @@ public: std::optional ledgerIndex; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; GatewayBalancesHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -66,10 +66,8 @@ public: static auto const hotWalletValidator = validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string() && !value.is_array()) - { - return Error{ - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}}; - } + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}}; + // wallet needs to be an valid accountID or public key auto const wallets = value.is_array() ? value.as_array() : boost::json::array{value}; auto const getAccountID = [](auto const& j) -> std::optional { @@ -77,17 +75,22 @@ public: { auto const pk = ripple::parseBase58( ripple::TokenType::AccountPublic, j.as_string().c_str()); + if (pk) return ripple::calcAccountID(*pk); + return ripple::parseBase58(j.as_string().c_str()); } + return {}; }; + for (auto const& wallet : wallets) { if (!getAccountID(wallet)) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; } + return MaybeError{}; }}; @@ -111,4 +114,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/Ledger.cpp b/src/rpc/handlers/Ledger.cpp index 04dcb934..10c18e67 100644 --- a/src/rpc/handlers/Ledger.cpp +++ b/src/rpc/handlers/Ledger.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,160 +17,166 @@ */ //============================================================================== -#include -#include +#include namespace RPC { - -Result -doLedger(Context const& context) +LedgerHandler::Result +LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const { - auto params = context.params; - boost::json::object response = {}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - bool binary = false; - if (params.contains(JS(binary))) + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; + + auto const lgrInfo = std::get(lgrInfoOrStatus); + Output output; + + if (input.binary) { - if (!params.at(JS(binary)).is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"}; - - binary = params.at(JS(binary)).as_bool(); - } - - bool transactions = false; - if (params.contains(JS(transactions))) - { - if (!params.at(JS(transactions)).is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "transactionsFlagNotBool"}; - - transactions = params.at(JS(transactions)).as_bool(); - } - - bool expand = false; - if (params.contains(JS(expand))) - { - if (!params.at(JS(expand)).is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "expandFlagNotBool"}; - - expand = params.at(JS(expand)).as_bool(); - } - - bool diff = false; - if (params.contains("diff")) - { - if (!params.at("diff").is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "diffFlagNotBool"}; - - diff = params.at("diff").as_bool(); - } - - if (params.contains(JS(full))) - return Status{RippledError::rpcNOT_SUPPORTED}; - - if (params.contains(JS(accounts))) - return Status{RippledError::rpcNOT_SUPPORTED}; - - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - - boost::json::object header; - if (binary) - { - header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); + output.header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); } else { - header[JS(accepted)] = true; - header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); - header[JS(close_flags)] = lgrInfo.closeFlags; - header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); - header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); - header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); - header[JS(closed)] = true; - header[JS(hash)] = ripple::strHex(lgrInfo.hash); - header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - header[JS(ledger_index)] = std::to_string(lgrInfo.seq); - header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); - header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); - header[JS(seqNum)] = std::to_string(lgrInfo.seq); - header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); - header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); - header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); + output.header[JS(accepted)] = true; + output.header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); + output.header[JS(close_flags)] = lgrInfo.closeFlags; + output.header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); + output.header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); + output.header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); + output.header[JS(closed)] = true; + output.header[JS(hash)] = ripple::strHex(lgrInfo.hash); + output.header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + output.header[JS(ledger_index)] = std::to_string(lgrInfo.seq); + output.header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); + output.header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); + output.header[JS(seqNum)] = std::to_string(lgrInfo.seq); + output.header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); + output.header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); + output.header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); } - header[JS(closed)] = true; - if (transactions) + output.header[JS(closed)] = true; + + if (input.transactions) { - header[JS(transactions)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonTxs = header.at(JS(transactions)).as_array(); - if (expand) + output.header[JS(transactions)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonTxs = output.header.at(JS(transactions)).as_array(); + + if (input.expand) { - auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq, context.yield); + auto txns = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield); std::transform( std::move_iterator(txns.begin()), std::move_iterator(txns.end()), std::back_inserter(jsonTxs), - [binary](auto obj) { + [&](auto obj) { boost::json::object entry; - if (!binary) + if (!input.binary) { auto [txn, meta] = toExpandedJson(obj); - entry = txn; - entry[JS(metaData)] = meta; + entry = std::move(txn); + entry[JS(metaData)] = std::move(meta); } else { entry[JS(tx_blob)] = ripple::strHex(obj.transaction); entry[JS(meta)] = ripple::strHex(obj.metadata); } - // entry[JS(ledger_index)] = obj.ledgerSequence; + return entry; }); } else { - auto hashes = context.backend->fetchAllTransactionHashesInLedger(lgrInfo.seq, context.yield); + auto hashes = sharedPtrBackend_->fetchAllTransactionHashesInLedger(lgrInfo.seq, ctx.yield); std::transform( std::move_iterator(hashes.begin()), std::move_iterator(hashes.end()), std::back_inserter(jsonTxs), - [](auto hash) { - boost::json::object entry; - return boost::json::string(ripple::strHex(hash)); - }); + [](auto hash) { return boost::json::string(ripple::strHex(hash)); }); } } - if (diff) + if (input.diff) { - header["diff"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonDiff = header.at("diff").as_array(); - auto diff = context.backend->fetchLedgerDiff(lgrInfo.seq, context.yield); + output.header["diff"] = boost::json::value(boost::json::array_kind); + + boost::json::array& jsonDiff = output.header.at("diff").as_array(); + auto diff = sharedPtrBackend_->fetchLedgerDiff(lgrInfo.seq, ctx.yield); + for (auto const& obj : diff) { boost::json::object entry; entry["object_id"] = ripple::strHex(obj.key); - if (binary) + + if (input.binary) + { entry["object"] = ripple::strHex(obj.blob); + } else if (obj.blob.size()) { - ripple::STLedgerEntry sle{ripple::SerialIter{obj.blob.data(), obj.blob.size()}, obj.key}; + ripple::STLedgerEntry const sle{ripple::SerialIter{obj.blob.data(), obj.blob.size()}, obj.key}; entry["object"] = toJson(sle); } else + { entry["object"] = ""; + } + jsonDiff.push_back(std::move(entry)); } } - response[JS(ledger)] = header; - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; - return response; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + output.ledgerIndex = lgrInfo.seq; + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(ledger), output.header}, + }; +} + +LedgerHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = LedgerHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(transactions))) + input.transactions = jv.at(JS(transactions)).as_bool(); + + if (jsonObject.contains(JS(binary))) + input.binary = jv.at(JS(binary)).as_bool(); + + if (jsonObject.contains(JS(expand))) + input.expand = jv.at(JS(expand)).as_bool(); + + if (jsonObject.contains("diff")) + input.diff = jv.at("diff").as_bool(); + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/Ledger.h b/src/rpc/handlers/Ledger.h similarity index 97% rename from src/rpc/ngHandlers/Ledger.h rename to src/rpc/handlers/Ledger.h index 464e77ee..622fa420 100644 --- a/src/rpc/ngHandlers/Ledger.h +++ b/src/rpc/handlers/Ledger.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class LedgerHandler { std::shared_ptr sharedPtrBackend_; @@ -52,7 +52,7 @@ public: bool diff = false; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; LedgerHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -86,4 +86,4 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 9b887d6e..61a5ef0e 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,98 +17,40 @@ */ //============================================================================== +#include + #include -#include -#include -#include -#include -#include - -// Get state nodes from a ledger -// Inputs: -// limit: integer, maximum number of entries -// marker: opaque, resume point -// binary: boolean, format -// type: string // optional, defaults to all ledger node types -// Outputs: -// ledger_hash: chosen ledger's hash -// ledger_index: chosen ledger's index -// state: array of state nodes -// marker: resume point, if any -// -// - -using namespace clio; - -// local to compilation unit loggers -namespace { -clio::Logger gLog{"RPC"}; -} // namespace +#include namespace RPC { -using boost::json::value_to; - -Result -doLedgerData(Context const& context) +LedgerDataHandler::Result +LedgerDataHandler::process(Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; + // marker must be int if outOfOrder is true + if (input.outOfOrder && input.marker) + return Error{Status{RippledError::rpcINVALID_PARAMS, "outOfOrderMarkerNotInt"}}; - bool const binary = getBool(request, "binary", false); + if (!input.outOfOrder && input.diffMarker) + return Error{Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}}; - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - if (!binary) - limit = std::clamp(limit, {1}, {256}); + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - bool outOfOrder = false; - if (request.contains("out_of_order")) + auto const lgrInfo = std::get(lgrInfoOrStatus); + + // no marker -> first call, return header information + auto header = boost::json::object(); + Output output; + + if ((!input.marker) && (!input.diffMarker)) { - if (!request.at("out_of_order").is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"}; - outOfOrder = request.at("out_of_order").as_bool(); - } - - std::optional marker; - std::optional diffMarker; - if (request.contains(JS(marker))) - { - if (!request.at(JS(marker)).is_string()) - { - if (outOfOrder) - { - if (!request.at(JS(marker)).is_int64()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotStringOrInt"}; - diffMarker = value_to(request.at(JS(marker))); - } - else - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - } - else - { - gLog.debug() << "Parsing marker"; - - marker = ripple::uint256{}; - if (!marker->parseHex(request.at(JS(marker)).as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "markerMalformed"}; - } - } - - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - - boost::json::object header; - // no marker means this is the first call, so we return header info - if (!request.contains(JS(marker))) - { - if (binary) + if (input.binary) { header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); } @@ -132,79 +74,155 @@ doLedgerData(Context const& context) } header[JS(closed)] = true; - response[JS(ledger)] = header; + output.header = std::move(header); } else { - if (!outOfOrder && !context.backend->fetchLedgerObject(*marker, lgrInfo.seq, context.yield)) - return Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}; + if (input.marker && !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield)) + return Error{Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}}; } - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + output.ledgerIndex = lgrInfo.seq; - auto start = std::chrono::system_clock::now(); + auto const start = std::chrono::system_clock::now(); std::vector results; - if (diffMarker) + + if (input.diffMarker) { - assert(outOfOrder); - auto diff = context.backend->fetchLedgerDiff(*diffMarker, context.yield); + // keep the same logic as previous implementation + auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield); std::vector keys; - for (auto&& [key, object] : diff) + + for (auto& [key, object] : diff) { if (!object.size()) - { keys.push_back(std::move(key)); - } } - auto objs = context.backend->fetchLedgerObjects(keys, lgrInfo.seq, context.yield); + + auto objs = sharedPtrBackend_->fetchLedgerObjects(keys, lgrInfo.seq, ctx.yield); + for (size_t i = 0; i < objs.size(); ++i) { - auto&& obj = objs[i]; + auto& obj = objs[i]; if (obj.size()) results.push_back({std::move(keys[i]), std::move(obj)}); } - if (*diffMarker > lgrInfo.seq) - response["marker"] = *diffMarker - 1; + + if (*(input.diffMarker) > lgrInfo.seq) + output.diffMarker = *(input.diffMarker) - 1; } else { - auto page = context.backend->fetchLedgerPage(marker, lgrInfo.seq, limit, outOfOrder, context.yield); + // limit's limitation is different based on binary or json + // framework can not handler the check right now, adjust the value here + auto const limit = + std::min(input.limit, input.binary ? LedgerDataHandler::LIMITBINARY : LedgerDataHandler::LIMITJSON); + auto page = sharedPtrBackend_->fetchLedgerPage(input.marker, lgrInfo.seq, limit, input.outOfOrder, ctx.yield); results = std::move(page.objects); + if (page.cursor) - response["marker"] = ripple::strHex(*(page.cursor)); - else if (outOfOrder) - response["marker"] = context.backend->fetchLedgerRange()->maxSequence; + output.marker = ripple::strHex(*(page.cursor)); + else if (input.outOfOrder) + output.diffMarker = sharedPtrBackend_->fetchLedgerRange()->maxSequence; } - auto end = std::chrono::system_clock::now(); - auto time = std::chrono::duration_cast(end - start).count(); + auto const end = std::chrono::system_clock::now(); + log_.debug() << "Number of results = " << results.size() << " fetched in " + << std::chrono::duration_cast(end - start).count() << " microseconds"; + + output.states.reserve(results.size()); - gLog.debug() << "Number of results = " << results.size() << " fetched in " << time << " microseconds"; - boost::json::array objects; - objects.reserve(results.size()); for (auto const& [key, object] : results) { - ripple::STLedgerEntry sle{ripple::SerialIter{object.data(), object.size()}, key}; - if (binary) + ripple::STLedgerEntry const sle{ripple::SerialIter{object.data(), object.size()}, key}; + + if (input.binary) { boost::json::object entry; entry[JS(data)] = ripple::serializeHex(sle); entry[JS(index)] = ripple::to_string(sle.key()); - objects.push_back(std::move(entry)); + output.states.push_back(std::move(entry)); } else - objects.push_back(toJson(sle)); + { + output.states.push_back(toJson(sle)); + } } - response[JS(state)] = std::move(objects); - if (outOfOrder) - response["cache_full"] = context.backend->cache().isFull(); - auto end2 = std::chrono::system_clock::now(); - time = std::chrono::duration_cast(end2 - end).count(); - gLog.debug() << "Number of results = " << results.size() << " serialized in " << time << " microseconds"; + if (input.outOfOrder) + output.cacheFull = sharedPtrBackend_->cache().isFull(); - return response; + auto const end2 = std::chrono::system_clock::now(); + log_.debug() << "Number of results = " << results.size() << " serialized in " + << std::chrono::duration_cast(end2 - end).count() << " microseconds"; + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerDataHandler::Output const& output) +{ + auto obj = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(state), output.states}, + }; + + if (output.header) + obj[JS(ledger)] = *(output.header); + + if (output.cacheFull) + obj["cache_full"] = *(output.cacheFull); + + if (output.diffMarker) + obj[JS(marker)] = *(output.diffMarker); + + else if (output.marker) + obj[JS(marker)] = *(output.marker); + + jv = std::move(obj); +} + +LedgerDataHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = LedgerDataHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(binary))) + { + input.binary = jsonObject.at(JS(binary)).as_bool(); + input.limit = input.binary ? LedgerDataHandler::LIMITBINARY : LedgerDataHandler::LIMITJSON; + } + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains("out_of_order")) + input.outOfOrder = jsonObject.at("out_of_order").as_bool(); + + if (jsonObject.contains("marker")) + { + if (jsonObject.at("marker").is_string()) + input.marker = ripple::uint256{jsonObject.at("marker").as_string().c_str()}; + else + input.diffMarker = jsonObject.at("marker").as_int64(); + } + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/LedgerData.h b/src/rpc/handlers/LedgerData.h similarity index 96% rename from src/rpc/ngHandlers/LedgerData.h rename to src/rpc/handlers/LedgerData.h index 11ac167e..d082489a 100644 --- a/src/rpc/ngHandlers/LedgerData.h +++ b/src/rpc/handlers/LedgerData.h @@ -24,12 +24,17 @@ #include #include -namespace RPCng { +namespace RPC { class LedgerDataHandler { + // dependencies std::shared_ptr sharedPtrBackend_; clio::Logger log_{"RPC"}; + // constants + static uint32_t constexpr LIMITBINARY = 2048; + static uint32_t constexpr LIMITJSON = 256; + public: struct Output { @@ -57,7 +62,7 @@ public: bool outOfOrder = false; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; LedgerDataHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -83,13 +88,10 @@ public: process(Input input, Context const& ctx) const; private: - static uint32_t constexpr LIMITBINARY = 2048; - static uint32_t constexpr LIMITJSON = 256; - friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index fd0b183b..db312706 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,359 +17,223 @@ */ //============================================================================== -#include -#include -#include +#include -#include -#include -// { -// ledger_hash : -// ledger_index : -// ... -// } +#include namespace RPC { -using boost::json::value_to; - -Result -doLedgerEntry(Context const& context) +LedgerEntryHandler::Result +LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; - - bool const binary = getBool(request, "binary", false); - - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - ripple::uint256 key; - // the expected type of the entry object - auto expectedType = ripple::ltANY; - // Note: according to docs, only 1 of the below should be specified at any - // time. see https://xrpl.org/ledger_entry.html#ledger_entry - if (request.contains(JS(index))) + if (input.index) { - if (!request.at(JS(index)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"}; - - if (!key.parseHex(request.at(JS(index)).as_string().c_str())) - return Status{ClioError::rpcMALFORMED_REQUEST}; + key = ripple::uint256{std::string_view(*(input.index))}; } - else if (request.contains(JS(account_root))) + else if (input.accountRoot) { - if (!request.at(JS(account_root)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "account_rootNotString"}; - - auto const account = ripple::parseBase58(request.at(JS(account_root)).as_string().c_str()); - expectedType = ripple::ltACCOUNT_ROOT; - if (!account || account->isZero()) - return Status{ClioError::rpcMALFORMED_ADDRESS}; - else - key = ripple::keylet::account(*account).key; + key = ripple::keylet::account(*ripple::parseBase58(*(input.accountRoot))).key; } - else if (request.contains(JS(check))) + else if (input.directory) { - if (!request.at(JS(check)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "checkNotString"}; + auto const keyOrStatus = composeKeyFromDirectory(*input.directory); + if (auto const status = std::get_if(&keyOrStatus)) + return Error{*status}; - expectedType = ripple::ltCHECK; - if (!key.parseHex(request.at(JS(check)).as_string().c_str())) - { - return Status{RippledError::rpcINVALID_PARAMS, "checkMalformed"}; - } + key = std::get(keyOrStatus); } - else if (request.contains(JS(deposit_preauth))) + else if (input.offer) { - expectedType = ripple::ltDEPOSIT_PREAUTH; - if (!request.at(JS(deposit_preauth)).is_object()) - { - if (!request.at(JS(deposit_preauth)).is_string() || - !key.parseHex(request.at(JS(deposit_preauth)).as_string().c_str())) - { - return Status{RippledError::rpcINVALID_PARAMS, "deposit_preauthMalformed"}; - } - } - else if ( - !request.at(JS(deposit_preauth)).as_object().contains(JS(owner)) || - !request.at(JS(deposit_preauth)).as_object().at(JS(owner)).is_string()) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"}; - } - else if ( - !request.at(JS(deposit_preauth)).as_object().contains(JS(authorized)) || - !request.at(JS(deposit_preauth)).as_object().at(JS(authorized)).is_string()) - { - return Status{RippledError::rpcINVALID_PARAMS, "authorizedNotString"}; - } - else - { - boost::json::object const& deposit_preauth = request.at(JS(deposit_preauth)).as_object(); - - auto const owner = - ripple::parseBase58(deposit_preauth.at(JS(owner)).as_string().c_str()); - - auto const authorized = - ripple::parseBase58(deposit_preauth.at(JS(authorized)).as_string().c_str()); - - if (!owner) - return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"}; - else if (!authorized) - return Status{RippledError::rpcINVALID_PARAMS, "malformedAuthorized"}; - else - key = ripple::keylet::depositPreauth(*owner, *authorized).key; - } + auto const id = ripple::parseBase58(input.offer->at(JS(account)).as_string().c_str()); + key = ripple::keylet::offer(*id, boost::json::value_to(input.offer->at(JS(seq)))).key; } - else if (request.contains(JS(directory))) + else if (input.rippleStateAccount) { - expectedType = ripple::ltDIR_NODE; - if (!request.at(JS(directory)).is_object()) - { - if (!request.at(JS(directory)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "directoryNotString"}; - - if (!key.parseHex(request.at(JS(directory)).as_string().c_str())) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedDirectory"}; - } - } - else if ( - request.at(JS(directory)).as_object().contains(JS(sub_index)) && - !request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64()) - { - return Status{RippledError::rpcINVALID_PARAMS, "sub_indexNotInt"}; - } - else - { - auto directory = request.at(JS(directory)).as_object(); - std::uint64_t subIndex = directory.contains(JS(sub_index)) - ? boost::json::value_to(directory.at(JS(sub_index))) - : 0; - - if (directory.contains(JS(dir_root))) - { - ripple::uint256 uDirRoot; - - if (directory.contains(JS(owner))) - { - // May not specify both dir_root and owner. - return Status{RippledError::rpcINVALID_PARAMS, "mayNotSpecifyBothDirRootAndOwner"}; - } - else if (!uDirRoot.parseHex(directory.at(JS(dir_root)).as_string().c_str())) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedDirRoot"}; - } - else - { - key = ripple::keylet::page(uDirRoot, subIndex).key; - } - } - else if (directory.contains(JS(owner))) - { - auto const ownerID = - ripple::parseBase58(directory.at(JS(owner)).as_string().c_str()); - - if (!ownerID) - { - return Status{ClioError::rpcMALFORMED_ADDRESS}; - } - else - { - key = ripple::keylet::page(ripple::keylet::ownerDir(*ownerID), subIndex).key; - } - } - else - { - return Status{RippledError::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"}; - } - } - } - else if (request.contains(JS(escrow))) - { - expectedType = ripple::ltESCROW; - if (!request.at(JS(escrow)).is_object()) - { - if (!key.parseHex(request.at(JS(escrow)).as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "malformedEscrow"}; - } - else if ( - !request.at(JS(escrow)).as_object().contains(JS(owner)) || - !request.at(JS(escrow)).as_object().at(JS(owner)).is_string()) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"}; - } - else if ( - !request.at(JS(escrow)).as_object().contains(JS(seq)) || - !request.at(JS(escrow)).as_object().at(JS(seq)).is_int64()) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedSeq"}; - } - else - { - auto const id = ripple::parseBase58( - request.at(JS(escrow)).as_object().at(JS(owner)).as_string().c_str()); - - if (!id) - return Status{ClioError::rpcMALFORMED_ADDRESS}; - else - { - std::uint32_t seq = request.at(JS(escrow)).as_object().at(JS(seq)).as_int64(); - key = ripple::keylet::escrow(*id, seq).key; - } - } - } - else if (request.contains(JS(offer))) - { - expectedType = ripple::ltOFFER; - if (!request.at(JS(offer)).is_object()) - { - if (!key.parseHex(request.at(JS(offer)).as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "malformedOffer"}; - } - else if ( - !request.at(JS(offer)).as_object().contains(JS(account)) || - !request.at(JS(offer)).as_object().at(JS(account)).is_string()) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"}; - } - else if ( - !request.at(JS(offer)).as_object().contains(JS(seq)) || - !request.at(JS(offer)).as_object().at(JS(seq)).is_int64()) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedSeq"}; - } - else - { - auto offer = request.at(JS(offer)).as_object(); - auto const id = ripple::parseBase58(offer.at(JS(account)).as_string().c_str()); - - if (!id) - return Status{ClioError::rpcMALFORMED_ADDRESS}; - else - { - std::uint32_t seq = boost::json::value_to(offer.at(JS(seq))); - key = ripple::keylet::offer(*id, seq).key; - } - } - } - else if (request.contains(JS(payment_channel))) - { - expectedType = ripple::ltPAYCHAN; - if (!request.at(JS(payment_channel)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "paymentChannelNotString"}; - - if (!key.parseHex(request.at(JS(payment_channel)).as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "malformedPaymentChannel"}; - } - else if (request.contains(JS(ripple_state))) - { - if (!request.at(JS(ripple_state)).is_object()) - return Status{RippledError::rpcINVALID_PARAMS, "rippleStateNotObject"}; - - expectedType = ripple::ltRIPPLE_STATE; - ripple::Currency currency; - boost::json::object const& state = request.at(JS(ripple_state)).as_object(); - - if (!state.contains(JS(currency)) || !state.at(JS(currency)).is_string()) - { - return Status{RippledError::rpcINVALID_PARAMS, "currencyNotString"}; - } - - if (!state.contains(JS(accounts)) || !state.at(JS(accounts)).is_array() || - 2 != state.at(JS(accounts)).as_array().size() || !state.at(JS(accounts)).as_array().at(0).is_string() || - !state.at(JS(accounts)).as_array().at(1).is_string() || - (state.at(JS(accounts)).as_array().at(0).as_string() == - state.at(JS(accounts)).as_array().at(1).as_string())) - { - return Status{RippledError::rpcINVALID_PARAMS, "malformedAccounts"}; - } - - auto const id1 = - ripple::parseBase58(state.at(JS(accounts)).as_array().at(0).as_string().c_str()); - auto const id2 = - ripple::parseBase58(state.at(JS(accounts)).as_array().at(1).as_string().c_str()); - - if (!id1 || !id2) - return Status{ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}; - - else if (!ripple::to_currency(currency, state.at(JS(currency)).as_string().c_str())) - return Status{ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"}; + auto const id1 = ripple::parseBase58( + input.rippleStateAccount->at(JS(accounts)).as_array().at(0).as_string().c_str()); + auto const id2 = ripple::parseBase58( + input.rippleStateAccount->at(JS(accounts)).as_array().at(1).as_string().c_str()); + auto const currency = ripple::to_currency(input.rippleStateAccount->at(JS(currency)).as_string().c_str()); key = ripple::keylet::line(*id1, *id2, currency).key; } - else if (request.contains(JS(ticket))) + else if (input.escrow) { - expectedType = ripple::ltTICKET; - // ticket object : account, ticket_seq - if (!request.at(JS(ticket)).is_object()) - { - if (!request.at(JS(ticket)).is_string()) - return Status{ClioError::rpcMALFORMED_REQUEST, "ticketNotString"}; + auto const id = ripple::parseBase58(input.escrow->at(JS(owner)).as_string().c_str()); + key = ripple::keylet::escrow(*id, input.escrow->at(JS(seq)).as_int64()).key; + } + else if (input.depositPreauth) + { + auto const owner = + ripple::parseBase58(input.depositPreauth->at(JS(owner)).as_string().c_str()); + auto const authorized = + ripple::parseBase58(input.depositPreauth->at(JS(authorized)).as_string().c_str()); - if (!key.parseHex(request.at(JS(ticket)).as_string().c_str())) - return Status{ClioError::rpcMALFORMED_REQUEST, "malformedTicket"}; - } - else if ( - !request.at(JS(ticket)).as_object().contains(JS(account)) || - !request.at(JS(ticket)).as_object().at(JS(account)).is_string()) - { - return Status{ClioError::rpcMALFORMED_REQUEST}; - } - else if ( - !request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) || - !request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64()) - { - return Status{ClioError::rpcMALFORMED_REQUEST, "malformedTicketSeq"}; - } - else - { - auto const id = ripple::parseBase58( - request.at(JS(ticket)).as_object().at(JS(account)).as_string().c_str()); + key = ripple::keylet::depositPreauth(*owner, *authorized).key; + } + else if (input.ticket) + { + auto const id = ripple::parseBase58(input.ticket->at(JS(account)).as_string().c_str()); - if (!id) - return Status{ClioError::rpcMALFORMED_OWNER}; - else - { - std::uint32_t seq = request.at(JS(ticket)).as_object().at(JS(ticket_seq)).as_int64(); - - key = ripple::getTicketIndex(*id, seq); - } - } + key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64()); } else { - return Status{RippledError::rpcINVALID_PARAMS, "unknownOption"}; + // Must specify 1 of the following fields to indicate what type + return Error{Status{RippledError::rpcINVALID_PARAMS, "unknownOption"}}; } - auto dbResponse = context.backend->fetchLedgerObject(key, lgrInfo.seq, context.yield); + // check ledger exists + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - if (!dbResponse or dbResponse->size() == 0) - return Status{"entryNotFound"}; + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - // check expected type matches actual type - ripple::STLedgerEntry sle{ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key}; - if (expectedType != ripple::ltANY && sle.getType() != expectedType) - return Status{"unexpectedLedgerType"}; + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield); - response[JS(index)] = ripple::strHex(key); - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response[JS(ledger_index)] = lgrInfo.seq; + if (!ledgerObject || ledgerObject->size() == 0) + return Error{Status{"entryNotFound"}}; - if (binary) - { - response[JS(node_binary)] = ripple::strHex(*dbResponse); - } + ripple::STLedgerEntry const sle{ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key}; + + if (input.expectedType != ripple::ltANY && sle.getType() != input.expectedType) + return Error{Status{"unexpectedLedgerType"}}; + + auto output = LedgerEntryHandler::Output{}; + output.index = ripple::strHex(key); + output.ledgerIndex = lgrInfo.seq; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + + if (input.binary) + output.nodeBinary = ripple::strHex(*ledgerObject); else + output.node = toJson(sle); + + return output; +} + +std::variant +LedgerEntryHandler::composeKeyFromDirectory(boost::json::object const& directory) const noexcept +{ + // can not specify both dir_root and owner. + if (directory.contains(JS(dir_root)) && directory.contains(JS(owner))) + return Status{RippledError::rpcINVALID_PARAMS, "mayNotSpecifyBothDirRootAndOwner"}; + + // at least one should availiable + if (!(directory.contains(JS(dir_root)) || directory.contains(JS(owner)))) + return Status{RippledError::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"}; + + uint64_t const subIndex = + directory.contains(JS(sub_index)) ? boost::json::value_to(directory.at(JS(sub_index))) : 0; + + if (directory.contains(JS(dir_root))) { - response[JS(node)] = toJson(sle); + ripple::uint256 const uDirRoot{directory.at(JS(dir_root)).as_string().c_str()}; + return ripple::keylet::page(uDirRoot, subIndex).key; } - return response; + auto const ownerID = ripple::parseBase58(directory.at(JS(owner)).as_string().c_str()); + return ripple::keylet::page(ripple::keylet::ownerDir(*ownerID), subIndex).key; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerEntryHandler::Output const& output) +{ + auto object = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + {JS(index), output.index}, + }; + + if (output.nodeBinary) + object[JS(node_binary)] = *(output.nodeBinary); + else + object[JS(node)] = *(output.node); + + jv = std::move(object); +} + +LedgerEntryHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = LedgerEntryHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(binary))) + input.binary = jv.at(JS(binary)).as_bool(); + + // check all the protential index + static auto const indexFieldTypeMap = std::unordered_map{ + {JS(index), ripple::ltANY}, + {JS(directory), ripple::ltDIR_NODE}, + {JS(offer), ripple::ltOFFER}, + {JS(check), ripple::ltCHECK}, + {JS(escrow), ripple::ltESCROW}, + {JS(payment_channel), ripple::ltPAYCHAN}, + {JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH}, + {JS(ticket), ripple::ltTICKET}, + }; + + auto const indexFieldType = + std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) { + auto const& [field, _] = pair; + return jsonObject.contains(field) && jsonObject.at(field).is_string(); + }); + + if (indexFieldType != indexFieldTypeMap.end()) + { + input.index = jv.at(indexFieldType->first).as_string().c_str(); + input.expectedType = indexFieldType->second; + } + // check if request for account root + else if (jsonObject.contains(JS(account_root))) + { + input.accountRoot = jv.at(JS(account_root)).as_string().c_str(); + } + // no need to check if_object again, validator only allows string or object + else if (jsonObject.contains(JS(directory))) + { + input.directory = jv.at(JS(directory)).as_object(); + } + else if (jsonObject.contains(JS(offer))) + { + input.offer = jv.at(JS(offer)).as_object(); + } + else if (jsonObject.contains(JS(ripple_state))) + { + input.rippleStateAccount = jv.at(JS(ripple_state)).as_object(); + } + else if (jsonObject.contains(JS(escrow))) + { + input.escrow = jv.at(JS(escrow)).as_object(); + } + else if (jsonObject.contains(JS(deposit_preauth))) + { + input.depositPreauth = jv.at(JS(deposit_preauth)).as_object(); + } + else if (jsonObject.contains(JS(ticket))) + { + input.ticket = jv.at(JS(ticket)).as_object(); + } + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/LedgerEntry.h b/src/rpc/handlers/LedgerEntry.h similarity index 95% rename from src/rpc/ngHandlers/LedgerEntry.h rename to src/rpc/handlers/LedgerEntry.h index 4fb39623..a6fa9f7d 100644 --- a/src/rpc/ngHandlers/LedgerEntry.h +++ b/src/rpc/handlers/LedgerEntry.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class LedgerEntryHandler { @@ -63,7 +63,7 @@ public: std::optional ticket; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; LedgerEntryHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -80,11 +80,16 @@ public: if (!value.is_array() || value.as_array().size() != 2 || !value.as_array()[0].is_string() || !value.as_array()[1].is_string() || value.as_array()[0].as_string() == value.as_array()[1].as_string()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "malformedAccounts"}}; + { + return Error{Status{RippledError::rpcINVALID_PARAMS, "malformedAccounts"}}; + } + auto const id1 = ripple::parseBase58(value.as_array()[0].as_string().c_str()); auto const id2 = ripple::parseBase58(value.as_array()[1].as_string().c_str()); + if (!id1 || !id2) - return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}}; + return Error{Status{ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}}; + return MaybeError{}; }}; @@ -156,7 +161,7 @@ public: private: // dir_root and owner can not be both empty or filled at the same time // This function will return an error if this is the case - std::variant + std::variant composeKeyFromDirectory(boost::json::object const& directory) const noexcept; friend void @@ -165,4 +170,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/LedgerRange.cpp b/src/rpc/handlers/LedgerRange.cpp index 9a64f90e..0de01c62 100644 --- a/src/rpc/handlers/LedgerRange.cpp +++ b/src/rpc/handlers/LedgerRange.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,28 +17,29 @@ */ //============================================================================== -#include #include +#include + +#include namespace RPC { -Result -doLedgerRange(Context const& context) +LedgerRangeHandler::Result +LedgerRangeHandler::process() const { - boost::json::object response = {}; - - auto range = context.backend->fetchLedgerRange(); - if (!range) - { - return Status{RippledError::rpcNOT_READY, "rangeNotFound"}; - } + if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange) + return Output{*maybeRange}; else - { - response[JS(ledger_index_min)] = range->minSequence; - response[JS(ledger_index_max)] = range->maxSequence; - } + return Error{Status{RippledError::rpcNOT_READY, "rangeNotFound"}}; +} - return response; +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerRangeHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(ledger_index_min), output.range.minSequence}, + {JS(ledger_index_max), output.range.maxSequence}, + }; } } // namespace RPC diff --git a/src/rpc/ngHandlers/LedgerRange.h b/src/rpc/handlers/LedgerRange.h similarity index 97% rename from src/rpc/ngHandlers/LedgerRange.h rename to src/rpc/handlers/LedgerRange.h index 554dddb9..00a67ba2 100644 --- a/src/rpc/ngHandlers/LedgerRange.h +++ b/src/rpc/handlers/LedgerRange.h @@ -26,7 +26,7 @@ #include -namespace RPCng { +namespace RPC { class LedgerRangeHandler { @@ -51,4 +51,4 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTBuyOffers.cpp b/src/rpc/handlers/NFTBuyOffers.cpp similarity index 94% rename from src/rpc/ngHandlers/NFTBuyOffers.cpp rename to src/rpc/handlers/NFTBuyOffers.cpp index 355b4d89..2a063a37 100644 --- a/src/rpc/ngHandlers/NFTBuyOffers.cpp +++ b/src/rpc/handlers/NFTBuyOffers.cpp @@ -18,20 +18,21 @@ //============================================================================== #include -#include +#include #include #include using namespace ripple; -namespace RPCng { +namespace RPC { NFTBuyOffersHandler::Result NFTBuyOffersHandler::process(NFTBuyOffersHandler::Input input, Context const& ctx) const { auto const tokenID = uint256{input.nftID.c_str()}; auto const directory = keylet::nft_buys(tokenID); + return iterateOfferDirectory(input, tokenID, directory, ctx.yield); } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTBuyOffers.h b/src/rpc/handlers/NFTBuyOffers.h similarity index 94% rename from src/rpc/ngHandlers/NFTBuyOffers.h rename to src/rpc/handlers/NFTBuyOffers.h index cf2e5f12..3fc09631 100644 --- a/src/rpc/ngHandlers/NFTBuyOffers.h +++ b/src/rpc/handlers/NFTBuyOffers.h @@ -20,9 +20,9 @@ #pragma once #include -#include +#include -namespace RPCng { +namespace RPC { class NFTBuyOffersHandler : public NFTOffersHandlerBase { public: @@ -34,4 +34,4 @@ public: Result process(Input input, Context const& ctx) const; }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/NFTHistory.cpp b/src/rpc/handlers/NFTHistory.cpp index a49a93eb..84af70ca 100644 --- a/src/rpc/handlers/NFTHistory.cpp +++ b/src/rpc/handlers/NFTHistory.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,50 +17,196 @@ */ //============================================================================== -#include -#include +#include #include -using namespace clio; - -// local to compilation unit loggers -namespace { -clio::Logger gLog{"RPC"}; -} // namespace - namespace RPC { -Result -doNFTHistory(Context const& context) +// TODO: this is currently very similar to account_tx but its own copy for time +// being. we should aim to reuse common logic in some way in the future. +NFTHistoryHandler::Result +NFTHistoryHandler::process(NFTHistoryHandler::Input input, Context const& ctx) const { - auto const maybeTokenID = getNFTID(context.params); - if (auto const status = std::get_if(&maybeTokenID); status) - return *status; - auto const tokenID = std::get(maybeTokenID); + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto [minIndex, maxIndex] = *range; - constexpr std::string_view outerFuncName = __func__; - auto const maybeResponse = traverseTransactions( - context, - [&tokenID, &outerFuncName]( - std::shared_ptr const& backend, - std::uint32_t const limit, - bool const forward, - std::optional const& cursorIn, - boost::asio::yield_context& yield) -> Backend::TransactionsAndCursor { - auto const [txnsAndCursor, timeDiff] = util::timed([&, &tokenID = tokenID]() { - return backend->fetchNFTTransactions(tokenID, limit, forward, cursorIn, yield); - }); - gLog.info() << outerFuncName << " db fetch took " << timeDiff - << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); - return txnsAndCursor; - }); + if (input.ledgerIndexMin) + { + if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin) + return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}}; - if (auto const status = std::get_if(&maybeResponse); status) - return *status; - auto response = std::get(maybeResponse); + minIndex = *input.ledgerIndexMin; + } + + if (input.ledgerIndexMax) + { + if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax) + return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}}; + + maxIndex = *input.ledgerIndexMax; + } + + if (minIndex > maxIndex) + return Error{Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"}}; + + if (input.ledgerHash || input.ledgerIndex) + { + // rippled does not have this check + if (input.ledgerIndexMax || input.ledgerIndexMin) + return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}}; + + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); + + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; + + maxIndex = minIndex = std::get(lgrInfoOrStatus).seq; + } + + std::optional cursor; + + // if marker exists + if (input.marker) + { + cursor = {input.marker->ledger, input.marker->seq}; + } + else + { + if (input.forward) + cursor = {minIndex, 0}; + else + cursor = {maxIndex, INT32_MAX}; + } + + static auto constexpr limitDefault = 50; + + auto const limit = input.limit.value_or(limitDefault); + auto const tokenID = ripple::uint256{input.nftID.c_str()}; + + auto const [txnsAndCursor, timeDiff] = util::timed( + [&]() { return sharedPtrBackend_->fetchNFTTransactions(tokenID, limit, input.forward, cursor, ctx.yield); }); + log_.info() << "db fetch took " << timeDiff << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); + + Output response; + auto const [blobs, retCursor] = txnsAndCursor; + + if (retCursor) + response.marker = {retCursor->ledgerSequence, retCursor->transactionIndex}; + + for (auto const& txnPlusMeta : blobs) + { + // over the range + if ((txnPlusMeta.ledgerSequence < minIndex && !input.forward) || + (txnPlusMeta.ledgerSequence > maxIndex && input.forward)) + { + response.marker = std::nullopt; + break; + } + else if (txnPlusMeta.ledgerSequence > maxIndex && !input.forward) + { + log_.debug() << "Skipping over transactions from incomplete ledger"; + continue; + } + + boost::json::object obj; + + if (!input.binary) + { + auto [txn, meta] = toExpandedJson(txnPlusMeta); + obj[JS(meta)] = std::move(meta); + obj[JS(tx)] = std::move(txn); + obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence; + obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; + } + else + { + obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); + obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); + obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; + // only clio has this field + obj[JS(date)] = txnPlusMeta.date; + } + + obj[JS(validated)] = true; + response.transactions.push_back(obj); + } + + response.limit = input.limit; + response.nftID = ripple::to_string(tokenID); + response.ledgerIndexMin = minIndex; + response.ledgerIndexMax = maxIndex; - response[JS(nft_id)] = ripple::to_string(tokenID); return response; } +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTHistoryHandler::Output const& output) +{ + jv = { + {JS(nft_id), output.nftID}, + {JS(ledger_index_min), output.ledgerIndexMin}, + {JS(ledger_index_max), output.ledgerIndexMax}, + {JS(transactions), output.transactions}, + {JS(validated), output.validated}, + }; + + if (output.marker) + jv.as_object()[JS(marker)] = boost::json::value_from(*(output.marker)); + + if (output.limit) + jv.as_object()[JS(limit)] = *(output.limit); +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTHistoryHandler::Marker const& marker) +{ + jv = { + {JS(ledger), marker.ledger}, + {JS(seq), marker.seq}, + }; +} + +NFTHistoryHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto const& jsonObject = jv.as_object(); + auto input = NFTHistoryHandler::Input{}; + + input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index_min)) && jsonObject.at(JS(ledger_index_min)).as_int64() != -1) + input.ledgerIndexMin = jsonObject.at(JS(ledger_index_min)).as_int64(); + + if (jsonObject.contains(JS(ledger_index_max)) && jsonObject.at(JS(ledger_index_max)).as_int64() != -1) + input.ledgerIndexMax = jsonObject.at(JS(ledger_index_max)).as_int64(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + if (jsonObject.contains(JS(binary))) + input.binary = jsonObject.at(JS(binary)).as_bool(); + + if (jsonObject.contains(JS(forward))) + input.forward = jsonObject.at(JS(forward)).as_bool(); + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = NFTHistoryHandler::Marker{ + jsonObject.at(JS(marker)).as_object().at(JS(ledger)).as_int64(), + jsonObject.at(JS(marker)).as_object().at(JS(seq)).as_int64()}; + + return input; +} + } // namespace RPC diff --git a/src/rpc/ngHandlers/NFTHistory.h b/src/rpc/handlers/NFTHistory.h similarity index 92% rename from src/rpc/ngHandlers/NFTHistory.h rename to src/rpc/handlers/NFTHistory.h index e81b554e..2f82f75c 100644 --- a/src/rpc/ngHandlers/NFTHistory.h +++ b/src/rpc/handlers/NFTHistory.h @@ -25,7 +25,7 @@ #include #include -namespace RPCng { +namespace RPC { class NFTHistoryHandler { clio::Logger log_{"RPC"}; @@ -52,7 +52,7 @@ public: bool validated = true; }; - // TODO: we did not implement the "strict" field + // TODO: We did not implement the "strict" field struct Input { std::string nftID; @@ -68,7 +68,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; NFTHistoryHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -88,8 +88,7 @@ public: {JS(limit), validation::Type{}, validation::Between{1, 100}}, {JS(marker), validation::WithCustomError{ - validation::Type{}, - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidMarker"}}, + validation::Type{}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}}, validation::Section{ {JS(ledger), validation::Required{}, validation::Type{}}, {JS(seq), validation::Required{}, validation::Type{}}, @@ -112,4 +111,5 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/NFTInfo.cpp b/src/rpc/handlers/NFTInfo.cpp index bdfae1e3..7e0b957e 100644 --- a/src/rpc/handlers/NFTInfo.cpp +++ b/src/rpc/handlers/NFTInfo.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,48 +17,96 @@ */ //============================================================================== +#include +#include + #include #include -#include -#include -#include +using namespace ripple; +using namespace ::RPC; namespace RPC { -Result -doNFTInfo(Context const& context) +NFTInfoHandler::Result +NFTInfoHandler::process(NFTInfoHandler::Input input, Context const& ctx) const { - auto const request = context.params; - boost::json::object response = {}; + auto const tokenID = ripple::uint256{input.nftID.c_str()}; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto const maybeTokenID = getNFTID(request); - if (auto const status = std::get_if(&maybeTokenID); status) - return *status; - auto const tokenID = std::get(maybeTokenID); + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - auto const maybeLedgerInfo = ledgerInfoFromRequest(context); - if (auto const status = std::get_if(&maybeLedgerInfo); status) - return *status; - auto const lgrInfo = std::get(maybeLedgerInfo); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const maybeNft = sharedPtrBackend_->fetchNFT(tokenID, lgrInfo.seq, ctx.yield); - auto const dbResponse = context.backend->fetchNFT(tokenID, lgrInfo.seq, context.yield); - if (!dbResponse) - return Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"}; + if (not maybeNft.has_value()) + return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"}}; - response[JS(nft_id)] = ripple::strHex(dbResponse->tokenID); - response[JS(ledger_index)] = dbResponse->ledgerSequence; - response[JS(owner)] = ripple::toBase58(dbResponse->owner); - response["is_burned"] = dbResponse->isBurned; - response[JS(uri)] = ripple::strHex(dbResponse->uri); + auto const& nft = *maybeNft; + auto output = NFTInfoHandler::Output{}; - response[JS(flags)] = ripple::nft::getFlags(dbResponse->tokenID); - response["transfer_fee"] = ripple::nft::getTransferFee(dbResponse->tokenID); - response[JS(issuer)] = ripple::toBase58(ripple::nft::getIssuer(dbResponse->tokenID)); - response["nft_taxon"] = ripple::nft::toUInt32(ripple::nft::getTaxon(dbResponse->tokenID)); - response[JS(nft_serial)] = ripple::nft::getSerial(dbResponse->tokenID); + output.nftID = strHex(nft.tokenID); + output.ledgerIndex = nft.ledgerSequence; + output.owner = toBase58(nft.owner); + output.isBurned = nft.isBurned; + output.flags = nft::getFlags(nft.tokenID); + output.transferFee = nft::getTransferFee(nft.tokenID); + output.issuer = toBase58(nft::getIssuer(nft.tokenID)); + output.taxon = nft::toUInt32(nft::getTaxon(nft.tokenID)); + output.serial = nft::getSerial(nft.tokenID); - return response; + if (not nft.isBurned) + output.uri = strHex(nft.uri); + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTInfoHandler::Output const& output) +{ + // TODO: use JStrings when they become available + auto object = boost::json::object{ + {JS(nft_id), output.nftID}, + {JS(ledger_index), output.ledgerIndex}, + {JS(owner), output.owner}, + {"is_burned", output.isBurned}, + {JS(flags), output.flags}, + {"transfer_fee", output.transferFee}, + {JS(issuer), output.issuer}, + {"nft_taxon", output.taxon}, + {JS(nft_serial), output.serial}, + {JS(validated), output.validated}, + }; + + if (output.uri) + object[JS(uri)] = *(output.uri); + + jv = std::move(object); +} + +NFTInfoHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto const& jsonObject = jv.as_object(); + auto input = NFTInfoHandler::Input{}; + + input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/NFTInfo.h b/src/rpc/handlers/NFTInfo.h similarity index 96% rename from src/rpc/ngHandlers/NFTInfo.h rename to src/rpc/handlers/NFTInfo.h index c65abc4c..4adea98f 100644 --- a/src/rpc/ngHandlers/NFTInfo.h +++ b/src/rpc/handlers/NFTInfo.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class NFTInfoHandler { std::shared_ptr sharedPtrBackend_; @@ -56,7 +56,7 @@ public: std::optional ledgerIndex; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; NFTInfoHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -84,4 +84,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/NFTOffers.cpp b/src/rpc/handlers/NFTOffers.cpp deleted file mode 100644 index b76df333..00000000 --- a/src/rpc/handlers/NFTOffers.cpp +++ /dev/null @@ -1,184 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace json = boost::json; - -namespace ripple { - -inline void -tag_invoke(json::value_from_tag, json::value& jv, SLE const& offer) -{ - auto amount = ::RPC::toBoostJson(offer.getFieldAmount(sfAmount).getJson(JsonOptions::none)); - - json::object obj = { - {JS(nft_offer_index), to_string(offer.key())}, - {JS(flags), offer[sfFlags]}, - {JS(owner), toBase58(offer.getAccountID(sfOwner))}, - {JS(amount), std::move(amount)}, - }; - - if (offer.isFieldPresent(sfDestination)) - obj.insert_or_assign(JS(destination), toBase58(offer.getAccountID(sfDestination))); - - if (offer.isFieldPresent(sfExpiration)) - obj.insert_or_assign(JS(expiration), offer.getFieldU32(sfExpiration)); - - jv = std::move(obj); -} - -} // namespace ripple - -namespace RPC { - -Result -enumerateNFTOffers(Context const& context, ripple::uint256 const& tokenid, ripple::Keylet const& directory) -{ - auto const& request = context.params; - - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - - // TODO: just check for existence without pulling - if (!context.backend->fetchLedgerObject(directory.key, lgrInfo.seq, context.yield)) - return Status{RippledError::rpcOBJECT_NOT_FOUND, "notFound"}; - - std::uint32_t limit; - if (auto const status = getLimit(context, limit); status) - return status; - - boost::json::object response = {}; - boost::json::array jsonOffers = {}; - response[JS(nft_id)] = ripple::to_string(tokenid); - - std::vector offers; - auto reserve = limit; - ripple::uint256 cursor; - uint64_t startHint = 0; - - if (request.contains(JS(marker))) - { - // We have a start point. Use limit - 1 from the result and use the - // very last one for the resume. - auto const& marker(request.at(JS(marker))); - - if (!marker.is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}; - - if (!cursor.parseHex(marker.as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "malformedCursor"}; - - auto const sle = read(ripple::keylet::nftoffer(cursor), lgrInfo, context); - - if (!sle || sle->getFieldU16(ripple::sfLedgerEntryType) != ripple::ltNFTOKEN_OFFER || - tokenid != sle->getFieldH256(ripple::sfNFTokenID)) - return Status{RippledError::rpcINVALID_PARAMS}; - - startHint = sle->getFieldU64(ripple::sfNFTokenOfferNode); - jsonOffers.push_back(json::value_from(*sle)); - offers.reserve(reserve); - } - else - { - // We have no start point, limit should be one higher than requested. - offers.reserve(++reserve); - } - - auto result = traverseOwnedNodes( - *context.backend, - directory, - cursor, - startHint, - lgrInfo.seq, - reserve, - {}, - context.yield, - [&offers](ripple::SLE&& offer) { - if (offer.getType() == ripple::ltNFTOKEN_OFFER) - { - offers.push_back(std::move(offer)); - return true; - } - - return false; - }); - - if (auto status = std::get_if(&result)) - return *status; - - if (offers.size() == reserve) - { - response[JS(limit)] = limit; - response[JS(marker)] = to_string(offers.back().key()); - offers.pop_back(); - } - - std::transform(std::cbegin(offers), std::cend(offers), std::back_inserter(jsonOffers), [](auto const& offer) { - // uses tag_invoke at the top of this file - return json::value_from(offer); - }); - - response.insert_or_assign(JS(offers), std::move(jsonOffers)); - return response; -} - -Result -doNFTOffers(Context const& context, bool sells) -{ - auto const v = getNFTID(context.params); - if (auto const status = std::get_if(&v)) - return *status; - - auto const getKeylet = [sells, &v]() { - if (sells) - return ripple::keylet::nft_sells(std::get(v)); - - return ripple::keylet::nft_buys(std::get(v)); - }; - - return enumerateNFTOffers(context, std::get(v), getKeylet()); -} - -Result -doNFTSellOffers(Context const& context) -{ - return doNFTOffers(context, true); -} - -Result -doNFTBuyOffers(Context const& context) -{ - return doNFTOffers(context, false); -} - -} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTOffersCommon.cpp b/src/rpc/handlers/NFTOffersCommon.cpp similarity index 93% rename from src/rpc/ngHandlers/NFTOffersCommon.cpp rename to src/rpc/handlers/NFTOffersCommon.cpp index 83ef3221..af69e418 100644 --- a/src/rpc/ngHandlers/NFTOffersCommon.cpp +++ b/src/rpc/handlers/NFTOffersCommon.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -32,7 +32,7 @@ namespace ripple { inline void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, SLE const& offer) { - auto amount = ::RPC::toBoostJson(offer.getFieldAmount(sfAmount).getJson(JsonOptions::none)); + auto amount = ::toBoostJson(offer.getFieldAmount(sfAmount).getJson(JsonOptions::none)); boost::json::object obj = { {JS(nft_offer_index), to_string(offer.key())}, @@ -52,7 +52,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, SLE const& offer } // namespace ripple -namespace RPCng { +namespace RPC { NFTOffersHandlerBase::Result NFTOffersHandlerBase::iterateOfferDirectory( @@ -64,6 +64,7 @@ NFTOffersHandlerBase::iterateOfferDirectory( auto const range = sharedPtrBackend_->fetchLedgerRange(); auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); + if (auto const status = std::get_if(&lgrInfoOrStatus)) return Error{*status}; @@ -83,14 +84,13 @@ NFTOffersHandlerBase::iterateOfferDirectory( { cursor = uint256(input.marker->c_str()); - // We have a start point. Use limit - 1 from the result and use the - // very last one for the resume. + // We have a start point. Use limit - 1 from the result and use the very last one for the resume. auto const sle = [this, &cursor, &lgrInfo, &yield]() -> std::shared_ptr { auto const key = keylet::nftoffer(cursor).key; + if (auto const blob = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, yield); blob) - { return std::make_shared(SerialIter{blob->data(), blob->size()}, key); - } + return nullptr; }(); @@ -106,8 +106,7 @@ NFTOffersHandlerBase::iterateOfferDirectory( } else { - // We have no start point, limit should be one higher than - // requested. + // We have no start point, limit should be one higher than requested. offers.reserve(++reserve); } @@ -156,6 +155,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTOffersHandler if (output.marker) object[JS(marker)] = *(output.marker); + if (output.limit) object[JS(limit)] = *(output.limit); @@ -165,39 +165,29 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTOffersHandler NFTOffersHandlerBase::Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) { + auto input = NFTOffersHandlerBase::Input{}; auto const& jsonObject = jv.as_object(); - NFTOffersHandlerBase::Input input; input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); if (jsonObject.contains(JS(ledger_hash))) - { input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } if (jsonObject.contains(JS(ledger_index))) { if (!jsonObject.at(JS(ledger_index)).is_string()) - { input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } } if (jsonObject.contains(JS(marker))) - { input.marker = jsonObject.at(JS(marker)).as_string().c_str(); - } if (jsonObject.contains(JS(limit))) - { input.limit = jsonObject.at(JS(limit)).as_int64(); - } return input; } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTOffersCommon.h b/src/rpc/handlers/NFTOffersCommon.h similarity index 96% rename from src/rpc/ngHandlers/NFTOffersCommon.h rename to src/rpc/handlers/NFTOffersCommon.h index 1db9ba7c..d629624b 100644 --- a/src/rpc/ngHandlers/NFTOffersCommon.h +++ b/src/rpc/handlers/NFTOffersCommon.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class NFTOffersHandlerBase { @@ -51,7 +51,7 @@ public: std::optional marker; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; NFTOffersHandlerBase(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -88,4 +88,4 @@ private: tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTSellOffers.cpp b/src/rpc/handlers/NFTSellOffers.cpp similarity index 94% rename from src/rpc/ngHandlers/NFTSellOffers.cpp rename to src/rpc/handlers/NFTSellOffers.cpp index cfe20c7e..d2561ff1 100644 --- a/src/rpc/ngHandlers/NFTSellOffers.cpp +++ b/src/rpc/handlers/NFTSellOffers.cpp @@ -18,21 +18,22 @@ //============================================================================== #include -#include +#include #include #include using namespace ripple; -namespace RPCng { +namespace RPC { NFTSellOffersHandler::Result NFTSellOffersHandler::process(NFTSellOffersHandler::Input input, Context const& ctx) const { auto const tokenID = uint256{input.nftID.c_str()}; auto const directory = keylet::nft_sells(tokenID); + return iterateOfferDirectory(input, tokenID, directory, ctx.yield); } -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/NFTSellOffers.h b/src/rpc/handlers/NFTSellOffers.h similarity index 94% rename from src/rpc/ngHandlers/NFTSellOffers.h rename to src/rpc/handlers/NFTSellOffers.h index bee7c84c..f0aa976c 100644 --- a/src/rpc/ngHandlers/NFTSellOffers.h +++ b/src/rpc/handlers/NFTSellOffers.h @@ -20,9 +20,10 @@ #pragma once #include -#include +#include + +namespace RPC { -namespace RPCng { class NFTSellOffersHandler : public NFTOffersHandlerBase { public: @@ -34,4 +35,5 @@ public: Result process(Input input, Context const& ctx) const; }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 85e7ba40..7d5af44b 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -18,96 +18,84 @@ //============================================================================== #include -#include +#include +#include + +#include namespace RPC { -boost::json::object -getBaseTx(ripple::AccountID const& accountID, std::uint32_t accountSeq, ripple::Fees const& fees) +NoRippleCheckHandler::Result +NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ctx) const { - boost::json::object tx; - tx[JS(Sequence)] = accountSeq; - tx[JS(Account)] = ripple::toBase58(accountID); - tx[JS(Fee)] = RPC::toBoostJson(fees.units.jsonClipped()); - return tx; -} + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); -Result -doNoRippleCheck(Context const& context) -{ - auto const& request = context.params; + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - ripple::AccountID accountID; - if (auto const status = getAccount(request, accountID); status) - return status; + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const accountID = accountFromStringStrict(input.account); + auto const keylet = ripple::keylet::account(*accountID).key; + auto const accountObj = sharedPtrBackend_->fetchLedgerObject(keylet, lgrInfo.seq, ctx.yield); - std::string role = getRequiredString(request, "role"); - bool roleGateway = false; - { - if (role == "gateway") - roleGateway = true; - else if (role != "user") - return Status{RippledError::rpcINVALID_PARAMS, "role field is invalid"}; - } - - std::uint32_t limit = 300; - if (auto const status = getLimit(context, limit); status) - return status; - - bool includeTxs = getBool(request, "transactions", false); - - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; - - auto lgrInfo = std::get(v); - std::optional fees = - includeTxs ? context.backend->fetchFees(lgrInfo.seq, context.yield) : std::nullopt; - - boost::json::array transactions; - - auto keylet = ripple::keylet::account(accountID); - auto accountObj = context.backend->fetchLedgerObject(keylet.key, lgrInfo.seq, context.yield); if (!accountObj) - throw AccountNotFoundError(ripple::toBase58(accountID)); + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - ripple::SerialIter it{accountObj->data(), accountObj->size()}; - ripple::SLE sle{it, keylet.key}; + auto it = ripple::SerialIter{accountObj->data(), accountObj->size()}; + auto sle = ripple::SLE{it, keylet}; + auto accountSeq = sle.getFieldU32(ripple::sfSequence); + bool const bDefaultRipple = sle.getFieldU32(ripple::sfFlags) & ripple::lsfDefaultRipple; + auto const fees = input.transactions ? sharedPtrBackend_->fetchFees(lgrInfo.seq, ctx.yield) : std::nullopt; - std::uint32_t accountSeq = sle.getFieldU32(ripple::sfSequence); + auto output = NoRippleCheckHandler::Output(); - boost::json::array problems; - bool bDefaultRipple = sle.getFieldU32(ripple::sfFlags) & ripple::lsfDefaultRipple; - if (bDefaultRipple & !roleGateway) + if (input.transactions) + output.transactions.emplace(boost::json::array()); + + auto const getBaseTx = [&](ripple::AccountID const& accountID, std::uint32_t accountSeq) { + boost::json::object tx; + tx[JS(Sequence)] = accountSeq; + tx[JS(Account)] = ripple::toBase58(accountID); + tx[JS(Fee)] = toBoostJson(fees->units.jsonClipped()); + + return tx; + }; + + if (bDefaultRipple && !input.roleGateway) { - problems.push_back( + output.problems.push_back( "You appear to have set your default ripple flag even though " "you " "are not a gateway. This is not recommended unless you are " "experimenting"); } - else if (roleGateway & !bDefaultRipple) + else if (input.roleGateway && !bDefaultRipple) { - problems.push_back("You should immediately set your default ripple flag"); - if (includeTxs) + output.problems.push_back("You should immediately set your default ripple flag"); + + if (input.transactions) { - auto tx = getBaseTx(accountID, accountSeq++, *fees); - tx[JS(TransactionType)] = JS(AccountSet); - tx[JS(SetFlag)] = 8; - transactions.push_back(tx); + auto tx = getBaseTx(*accountID, accountSeq++); + tx[JS(TransactionType)] = "AccountSet"; + tx[JS(SetFlag)] = ripple::asfDefaultRipple; + output.transactions->push_back(tx); } } - traverseOwnedNodes( - *context.backend, - accountID, + auto limit = input.limit; + + ngTraverseOwnedNodes( + *sharedPtrBackend_, + *accountID, lgrInfo.seq, std::numeric_limits::max(), {}, - context.yield, - [roleGateway, includeTxs, &fees, &transactions, &accountSeq, &limit, &accountID, &problems]( - ripple::SLE&& ownedItem) { - if (ownedItem.getType() == ripple::ltRIPPLE_STATE) + ctx.yield, + [&](ripple::SLE&& ownedItem) { + // don't push to result if limit is reached + if (limit != 0 && ownedItem.getType() == ripple::ltRIPPLE_STATE) { bool const bLow = accountID == ownedItem.getFieldAmount(ripple::sfLowLimit).getIssuer(); @@ -116,12 +104,12 @@ doNoRippleCheck(Context const& context) std::string problem; bool needFix = false; - if (bNoRipple & roleGateway) + if (bNoRipple && input.roleGateway) { problem = "You should clear the no ripple flag on your "; needFix = true; } - else if (!bNoRipple & !roleGateway) + else if (!bNoRipple && !input.roleGateway) { problem = "You should probably set the no ripple flag on " @@ -130,41 +118,85 @@ doNoRippleCheck(Context const& context) } if (needFix) { + --limit; + ripple::AccountID peer = ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit).getIssuer(); ripple::STAmount peerLimit = ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); - problem += to_string(peerLimit.getCurrency()); - problem += " line to "; - problem += to_string(peerLimit.getIssuer()); - problems.emplace_back(problem); - if (includeTxs) + + problem += fmt::format( + "{} line to {}", to_string(peerLimit.getCurrency()), to_string(peerLimit.getIssuer())); + output.problems.emplace_back(problem); + + if (input.transactions) { ripple::STAmount limitAmount( ownedItem.getFieldAmount(bLow ? ripple::sfLowLimit : ripple::sfHighLimit)); limitAmount.setIssuer(peer); - auto tx = getBaseTx(accountID, accountSeq++, *fees); - tx[JS(TransactionType)] = JS(TrustSet); - tx[JS(LimitAmount)] = RPC::toBoostJson(limitAmount.getJson(ripple::JsonOptions::none)); - tx[JS(Flags)] = bNoRipple ? ripple::tfClearNoRipple : ripple::tfSetNoRipple; - transactions.push_back(tx); - } - if (limit-- == 0) - return false; + auto tx = getBaseTx(*accountID, accountSeq++); + + tx[JS(TransactionType)] = "TrustSet"; + tx[JS(LimitAmount)] = toBoostJson(limitAmount.getJson(ripple::JsonOptions::none)); + tx[JS(Flags)] = bNoRipple ? ripple::tfClearNoRipple : ripple::tfSetNoRipple; + + output.transactions->push_back(tx); + } } } + return true; }); - boost::json::object response; - response[JS(ledger_index)] = lgrInfo.seq; - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - response["problems"] = std::move(problems); - if (includeTxs) - response[JS(transactions)] = std::move(transactions); + output.ledgerIndex = lgrInfo.seq; + output.ledgerHash = ripple::strHex(lgrInfo.hash); - return response; + return output; +} + +NoRippleCheckHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = NoRippleCheckHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.account = jsonObject.at(JS(account)).as_string().c_str(); + input.roleGateway = jsonObject.at(JS(role)).as_string() == "gateway"; + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(transactions))) + input.transactions = jsonObject.at(JS(transactions)).as_bool(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NoRippleCheckHandler::Output const& output) +{ + auto obj = boost::json::object{ + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {"problems", output.problems}, + }; + + if (output.transactions) + obj.emplace(JS(transactions), *(output.transactions)); + + jv = std::move(obj); } } // namespace RPC diff --git a/src/rpc/ngHandlers/NoRippleCheck.h b/src/rpc/handlers/NoRippleCheck.h similarity index 93% rename from src/rpc/ngHandlers/NoRippleCheck.h rename to src/rpc/handlers/NoRippleCheck.h index 1ed8b497..4d12cfb8 100644 --- a/src/rpc/ngHandlers/NoRippleCheck.h +++ b/src/rpc/handlers/NoRippleCheck.h @@ -26,7 +26,7 @@ #include -namespace RPCng { +namespace RPC { class NoRippleCheckHandler { std::shared_ptr sharedPtrBackend_; @@ -52,7 +52,7 @@ public: bool transactions = false; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; NoRippleCheckHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -68,7 +68,7 @@ public: validation::Required{}, validation::WithCustomError{ validation::OneOf{"gateway", "user"}, - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "role field is invalid"}}}, + Status{RippledError::rpcINVALID_PARAMS, "role field is invalid"}}}, {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_index), validation::LedgerIndexValidator}, {JS(limit), validation::Type(), validation::Between{1, 500}}, @@ -88,4 +88,4 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/Ping.h b/src/rpc/handlers/Ping.h similarity index 96% rename from src/rpc/ngHandlers/Ping.h rename to src/rpc/handlers/Ping.h index 1fd769cd..2dec27af 100644 --- a/src/rpc/ngHandlers/Ping.h +++ b/src/rpc/handlers/Ping.h @@ -21,7 +21,7 @@ #include -namespace RPCng { +namespace RPC { class PingHandler { @@ -35,4 +35,5 @@ public: return Output{}; } }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/Random.cpp b/src/rpc/handlers/Random.cpp index 318e13f6..ad7ed86f 100644 --- a/src/rpc/handlers/Random.cpp +++ b/src/rpc/handlers/Random.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,24 +17,29 @@ */ //============================================================================== -#include +#include +#include #include #include -#include - namespace RPC { -Result -doRandom(Context const& context) +RandomHandler::Result +RandomHandler::process() const { ripple::uint256 rand; - beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng()); - boost::json::object result; - result[JS(random)] = ripple::strHex(rand); - return result; + + return Output{ripple::strHex(rand)}; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, RandomHandler::Output const& output) +{ + jv = { + {JS(random), output.random}, + }; } } // namespace RPC diff --git a/src/rpc/ngHandlers/Random.h b/src/rpc/handlers/Random.h similarity index 97% rename from src/rpc/ngHandlers/Random.h rename to src/rpc/handlers/Random.h index 29ebecff..40a1fa20 100644 --- a/src/rpc/ngHandlers/Random.h +++ b/src/rpc/handlers/Random.h @@ -25,7 +25,7 @@ #include -namespace RPCng { +namespace RPC { class RandomHandler { @@ -44,4 +44,5 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/ServerInfo.cpp b/src/rpc/handlers/ServerInfo.cpp deleted file mode 100644 index 8985180d..00000000 --- a/src/rpc/handlers/ServerInfo.cpp +++ /dev/null @@ -1,112 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPC { - -Result -doServerInfo(Context const& context) -{ - boost::json::object response = {}; - - auto range = context.backend->fetchLedgerRange(); - if (!range) - { - return Status{RippledError::rpcNOT_READY, "emptyDatabase", "The server has no data in the database"}; - } - - auto lgrInfo = context.backend->fetchLedgerBySequence(range->maxSequence, context.yield); - - auto fees = context.backend->fetchFees(lgrInfo->seq, context.yield); - - if (!lgrInfo || !fees) - return Status{RippledError::rpcINTERNAL}; - - auto age = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - - lgrInfo->closeTime.time_since_epoch().count() - 946684800; - - if (age < 0) - age = 0; - - response[JS(info)] = boost::json::object{}; - boost::json::object& info = response[JS(info)].as_object(); - - info[JS(complete_ledgers)] = std::to_string(range->minSequence) + "-" + std::to_string(range->maxSequence); - - bool admin = context.clientIp == "127.0.0.1"; - - if (admin) - { - info[JS(counters)] = context.counters.report(); - info[JS(counters)].as_object()["subscriptions"] = context.subscriptions->report(); - } - - auto serverInfoRippled = - context.balancer->forwardToRippled({{"command", "server_info"}}, context.clientIp, context.yield); - - info[JS(load_factor)] = 1; - info["clio_version"] = Build::getClioVersionString(); - if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) - { - try - { - auto& rippledResult = serverInfoRippled->at(JS(result)).as_object(); - auto& rippledInfo = rippledResult.at(JS(info)).as_object(); - info[JS(load_factor)] = rippledInfo[JS(load_factor)]; - info[JS(validation_quorum)] = rippledInfo[JS(validation_quorum)]; - info["rippled_version"] = rippledInfo[JS(build_version)]; - } - catch (std::exception const&) - { - } - } - - info[JS(validated_ledger)] = boost::json::object{}; - boost::json::object& validated = info[JS(validated_ledger)].as_object(); - - validated[JS(age)] = age; - validated[JS(hash)] = ripple::strHex(lgrInfo->hash); - validated[JS(seq)] = lgrInfo->seq; - validated[JS(base_fee_xrp)] = fees->base.decimalXRP(); - validated[JS(reserve_base_xrp)] = fees->reserve.decimalXRP(); - validated[JS(reserve_inc_xrp)] = fees->increment.decimalXRP(); - - info["cache"] = boost::json::object{}; - auto& cache = info["cache"].as_object(); - - cache["size"] = context.backend->cache().size(); - cache["is_full"] = context.backend->cache().isFull(); - cache["latest_ledger_seq"] = context.backend->cache().latestLedgerSequence(); - cache["object_hit_rate"] = context.backend->cache().getObjectHitRate(); - cache["successor_hit_rate"] = context.backend->cache().getSuccessorHitRate(); - - if (admin) - { - info["etl"] = context.etl->getInfo(); - } - - return response; -} -} // namespace RPC diff --git a/src/rpc/ngHandlers/ServerInfo.h b/src/rpc/handlers/ServerInfo.h similarity index 98% rename from src/rpc/ngHandlers/ServerInfo.h rename to src/rpc/handlers/ServerInfo.h index 432d7c5e..ba55f502 100644 --- a/src/rpc/ngHandlers/ServerInfo.h +++ b/src/rpc/handlers/ServerInfo.h @@ -37,7 +37,7 @@ namespace RPC { class Counters; } -namespace RPCng { +namespace RPC { template < typename SubscriptionManagerType, @@ -133,19 +133,20 @@ public: if (not fees.has_value()) return Error{Status{RippledError::rpcINTERNAL}}; + auto output = Output{}; auto const sinceEpoch = duration_cast(system_clock::now().time_since_epoch()).count(); auto const age = static_cast(sinceEpoch) - static_cast(lgrInfo->closeTime.time_since_epoch().count()) - static_cast(rippleEpochStart); - Output output; - output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence); + if (ctx.isAdmin) output.info.adminSection = {counters_.get().report(), subscriptions_->report(), etl_->getInfo()}; auto const serverInfoRippled = balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield); + if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) { if (serverInfoRippled->contains(JS(result)) && @@ -159,7 +160,6 @@ public: output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash); output.info.validatedLedger.seq = lgrInfo->seq; output.info.validatedLedger.fees = fees; - output.info.cache.size = backend_->cache().size(); output.info.cache.isFull = backend_->cache().isFull(); output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence(); @@ -195,6 +195,7 @@ private: try { auto const& rippledInfo = info.rippledInfo.value(); + jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor)); jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum)); jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version)); @@ -238,6 +239,6 @@ private: } }; -using ServerInfoHandler = BaseServerInfoHandler; +using ServerInfoHandler = BaseServerInfoHandler; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/Subscribe.cpp b/src/rpc/handlers/Subscribe.cpp deleted file mode 100644 index 9a1559f4..00000000 --- a/src/rpc/handlers/Subscribe.cpp +++ /dev/null @@ -1,428 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPC { - -// these are the streams that take no arguments -static std::unordered_set - validCommonStreams{"ledger", "transactions", "transactions_proposed", "validations", "manifests", "book_changes"}; - -Status -validateStreams(boost::json::object const& request) -{ - for (auto const& streams = request.at(JS(streams)).as_array(); auto const& stream : streams) - { - if (!stream.is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "streamNotString"}; - - if (!validCommonStreams.contains(stream.as_string().c_str())) - return Status{RippledError::rpcSTREAM_MALFORMED}; - } - - return OK; -} - -boost::json::object -subscribeToStreams( - boost::asio::yield_context& yield, - boost::json::object const& request, - std::shared_ptr session, - SubscriptionManager& manager) -{ - boost::json::array const& streams = request.at(JS(streams)).as_array(); - - boost::json::object response; - for (auto const& stream : streams) - { - std::string s = stream.as_string().c_str(); - - if (s == "ledger") - response = manager.subLedger(yield, session); - else if (s == "transactions") - manager.subTransactions(session); - else if (s == "transactions_proposed") - manager.subProposedTransactions(session); - else if (s == "validations") - manager.subValidation(session); - else if (s == "manifests") - manager.subManifest(session); - else if (s == "book_changes") - manager.subBookChanges(session); - else - assert(false); - } - return response; -} - -void -unsubscribeToStreams(boost::json::object const& request, std::shared_ptr session, SubscriptionManager& manager) -{ - boost::json::array const& streams = request.at(JS(streams)).as_array(); - - for (auto const& stream : streams) - { - std::string s = stream.as_string().c_str(); - - if (s == "ledger") - manager.unsubLedger(session); - else if (s == "transactions") - manager.unsubTransactions(session); - else if (s == "transactions_proposed") - manager.unsubProposedTransactions(session); - else if (s == "validations") - manager.unsubValidation(session); - else if (s == "manifests") - manager.unsubManifest(session); - else if (s == "book_changes") - manager.unsubBookChanges(session); - else - assert(false); - } -} - -Status -validateAccounts(boost::json::array const& accounts) -{ - for (auto const& account : accounts) - { - if (!account.is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "accountNotString"}; - - if (!accountFromStringStrict(account.as_string().c_str())) - return Status{RippledError::rpcACT_MALFORMED, "Account malformed."}; - } - - return OK; -} - -void -subscribeToAccounts(boost::json::object const& request, std::shared_ptr session, SubscriptionManager& manager) -{ - boost::json::array const& accounts = request.at(JS(accounts)).as_array(); - - for (auto const& account : accounts) - { - std::string s = account.as_string().c_str(); - - auto accountID = accountFromStringStrict(s); - - if (!accountID) - { - assert(false); - continue; - } - - manager.subAccount(*accountID, session); - } -} - -void -unsubscribeToAccounts(boost::json::object const& request, std::shared_ptr session, SubscriptionManager& manager) -{ - boost::json::array const& accounts = request.at(JS(accounts)).as_array(); - - for (auto const& account : accounts) - { - std::string s = account.as_string().c_str(); - - auto accountID = accountFromStringStrict(s); - - if (!accountID) - { - assert(false); - continue; - } - - manager.unsubAccount(*accountID, session); - } -} - -void -subscribeToAccountsProposed( - boost::json::object const& request, - std::shared_ptr session, - SubscriptionManager& manager) -{ - boost::json::array const& accounts = request.at(JS(accounts_proposed)).as_array(); - - for (auto const& account : accounts) - { - std::string s = account.as_string().c_str(); - - auto accountID = ripple::parseBase58(s); - - if (!accountID) - { - assert(false); - continue; - } - - manager.subProposedAccount(*accountID, session); - } -} - -void -unsubscribeToAccountsProposed( - boost::json::object const& request, - std::shared_ptr session, - SubscriptionManager& manager) -{ - boost::json::array const& accounts = request.at(JS(accounts_proposed)).as_array(); - - for (auto const& account : accounts) - { - std::string s = account.as_string().c_str(); - - auto accountID = ripple::parseBase58(s); - - if (!accountID) - { - assert(false); - continue; - } - - manager.unsubProposedAccount(*accountID, session); - } -} - -std::variant, boost::json::array>> -validateAndGetBooks( - boost::asio::yield_context& yield, - boost::json::object const& request, - std::shared_ptr const& backend) -{ - if (!request.at(JS(books)).is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "booksNotArray"}; - boost::json::array const& books = request.at(JS(books)).as_array(); - - std::vector booksToSub; - std::optional rng; - boost::json::array snapshot; - for (auto const& book : books) - { - auto parsedBook = parseBook(book.as_object()); - if (auto status = std::get_if(&parsedBook)) - return *status; - - auto b = std::get(parsedBook); - booksToSub.push_back(b); - bool both = book.as_object().contains(JS(both)); - if (both) - booksToSub.push_back(ripple::reversed(b)); - - if (book.as_object().contains(JS(snapshot))) - { - if (!rng) - rng = backend->fetchLedgerRange(); - ripple::AccountID takerID = beast::zero; - if (book.as_object().contains(JS(taker))) - if (auto const status = getTaker(book.as_object(), takerID); status) - return status; - - auto getOrderBook = [&snapshot, &backend, &rng, &takerID](auto book, boost::asio::yield_context& yield) { - auto bookBase = getBookBase(book); - auto [offers, _] = backend->fetchBookOffers(bookBase, rng->maxSequence, 200, yield); - - auto orderBook = postProcessOrderBook(offers, book, takerID, *backend, rng->maxSequence, yield); - std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshot)); - }; - getOrderBook(b, yield); - if (both) - getOrderBook(ripple::reversed(b), yield); - } - } - return std::make_pair(booksToSub, snapshot); -} - -void -subscribeToBooks(std::vector const& books, std::shared_ptr session, SubscriptionManager& manager) -{ - for (auto const& book : books) - { - manager.subBook(book, session); - } -} - -void -unsubscribeToBooks( - std::vector const& books, - std::shared_ptr session, - SubscriptionManager& manager) -{ - for (auto const& book : books) - { - manager.unsubBook(book, session); - } -} - -Result -doSubscribe(Context const& context) -{ - auto request = context.params; - - if (request.contains(JS(streams))) - { - if (!request.at(JS(streams)).is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "streamsNotArray"}; - - auto status = validateStreams(request); - - if (status) - return status; - } - - if (request.contains(JS(accounts))) - { - auto const& jsonAccounts = request.at(JS(accounts)); - if (!jsonAccounts.is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "accountsNotArray"}; - - auto const& accounts = jsonAccounts.as_array(); - if (accounts.empty()) - return Status{RippledError::rpcACT_MALFORMED, "Account malformed."}; - - auto const status = validateAccounts(accounts); - if (status) - return status; - } - - if (request.contains(JS(accounts_proposed))) - { - auto const& jsonAccounts = request.at(JS(accounts_proposed)); - if (!jsonAccounts.is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "accountsProposedNotArray"}; - - auto const& accounts = jsonAccounts.as_array(); - if (accounts.empty()) - return Status{RippledError::rpcACT_MALFORMED, "Account malformed."}; - - auto const status = validateAccounts(accounts); - if (status) - return status; - } - - std::vector books; - boost::json::object response; - - if (request.contains(JS(books))) - { - auto parsed = validateAndGetBooks(context.yield, request, context.backend); - if (auto status = std::get_if(&parsed)) - return *status; - auto [bks, snap] = std::get, boost::json::array>>(parsed); - books = std::move(bks); - response[JS(offers)] = std::move(snap); - } - - if (request.contains(JS(streams))) - response = subscribeToStreams(context.yield, request, context.session, *context.subscriptions); - - if (request.contains(JS(accounts))) - subscribeToAccounts(request, context.session, *context.subscriptions); - - if (request.contains(JS(accounts_proposed))) - subscribeToAccountsProposed(request, context.session, *context.subscriptions); - - if (request.contains(JS(books))) - subscribeToBooks(books, context.session, *context.subscriptions); - - return response; -} - -Result -doUnsubscribe(Context const& context) -{ - auto request = context.params; - - if (request.contains(JS(streams))) - { - if (!request.at(JS(streams)).is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "streamsNotArray"}; - - auto status = validateStreams(request); - - if (status) - return status; - } - - if (request.contains(JS(accounts))) - { - auto const& jsonAccounts = request.at(JS(accounts)); - if (!jsonAccounts.is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "accountsNotArray"}; - - auto const& accounts = jsonAccounts.as_array(); - if (accounts.empty()) - return Status{RippledError::rpcACT_MALFORMED, "Account malformed."}; - - auto const status = validateAccounts(accounts); - if (status) - return status; - } - - if (request.contains(JS(accounts_proposed))) - { - auto const& jsonAccounts = request.at(JS(accounts_proposed)); - if (!jsonAccounts.is_array()) - return Status{RippledError::rpcINVALID_PARAMS, "accountsProposedNotArray"}; - - auto const& accounts = jsonAccounts.as_array(); - if (accounts.empty()) - return Status{RippledError::rpcACT_MALFORMED, "Account malformed."}; - - auto const status = validateAccounts(accounts); - if (status) - return status; - } - - std::vector books; - if (request.contains(JS(books))) - { - auto parsed = validateAndGetBooks(context.yield, request, context.backend); - - if (auto status = std::get_if(&parsed)) - return *status; - - auto [bks, snap] = std::get, boost::json::array>>(parsed); - - books = std::move(bks); - } - - if (request.contains(JS(streams))) - unsubscribeToStreams(request, context.session, *context.subscriptions); - - if (request.contains(JS(accounts))) - unsubscribeToAccounts(request, context.session, *context.subscriptions); - - if (request.contains(JS(accounts_proposed))) - unsubscribeToAccountsProposed(request, context.session, *context.subscriptions); - - if (request.contains("books")) - unsubscribeToBooks(books, context.session, *context.subscriptions); - - return boost::json::object{}; -} - -} // namespace RPC diff --git a/src/rpc/ngHandlers/Subscribe.h b/src/rpc/handlers/Subscribe.h similarity index 87% rename from src/rpc/ngHandlers/Subscribe.h rename to src/rpc/handlers/Subscribe.h index 0a578e1b..a5453c1b 100644 --- a/src/rpc/ngHandlers/Subscribe.h +++ b/src/rpc/handlers/Subscribe.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { template class BaseSubscribeHandler { @@ -40,7 +40,6 @@ public: // books returns nothing by default, if snapshot is true, it returns offers // TODO: use better type than json std::optional offers; - bool validated = true; }; struct OrderBook @@ -59,7 +58,7 @@ public: std::optional> books; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; BaseSubscribeHandler( std::shared_ptr const& sharedPtrBackend, @@ -74,27 +73,28 @@ public: static auto const booksValidator = validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_array()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + for (auto const& book : value.as_array()) { if (!book.is_object()) - return Error{ - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}}; if (book.as_object().contains("both") && !book.as_object().at("both").is_bool()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "bothNotBool"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}}; if (book.as_object().contains("snapshot") && !book.as_object().at("snapshot").is_bool()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}}; if (book.as_object().contains("taker")) if (auto const err = validation::AccountValidator.verify(book.as_object(), "taker"); !err) return err; - auto const parsedBook = RPC::parseBook(book.as_object()); - if (auto const status = std::get_if(&parsedBook)) + auto const parsedBook = parseBook(book.as_object()); + if (auto const status = std::get_if(&parsedBook)) return Error(*status); } + return MaybeError{}; }}; @@ -102,30 +102,37 @@ public: {JS(streams), validation::SubscribeStreamValidator}, {JS(accounts), validation::SubscribeAccountsValidator}, {JS(accounts_proposed), validation::SubscribeAccountsValidator}, - {JS(books), booksValidator}}; + {JS(books), booksValidator}, + }; + return rpcSpec; } Result process(Input input, Context const& ctx) const { - Output output; + auto output = Output{}; + if (input.streams) { auto const ledger = subscribeToStreams(ctx.yield, *(input.streams), ctx.session); if (!ledger.empty()) output.ledger = ledger; } + if (input.accounts) subscribeToAccounts(*(input.accounts), ctx.session); + if (input.accountsProposed) subscribeToAccountsProposed(*(input.accountsProposed), ctx.session); + if (input.books) { auto const offers = subscribeToBooks(*(input.books), ctx.session, ctx.yield); if (!offers.empty()) output.offers = offers; }; + return output; } @@ -136,7 +143,8 @@ private: std::vector const& streams, std::shared_ptr const& session) const { - boost::json::object response; + auto response = boost::json::object{}; + for (auto const& stream : streams) { if (stream == "ledger") @@ -152,6 +160,7 @@ private: else if (stream == "book_changes") subscriptions_->subBookChanges(session); } + return response; } @@ -160,16 +169,17 @@ private: { for (auto const& account : accounts) { - auto const accountID = RPC::accountFromStringStrict(account); + auto const accountID = accountFromStringStrict(account); subscriptions_->subAccount(*accountID, session); } } + void subscribeToAccountsProposed(std::vector const& accounts, std::shared_ptr const& session) const { for (auto const& account : accounts) { - auto const accountID = RPC::accountFromStringStrict(account); + auto const accountID = accountFromStringStrict(account); subscriptions_->subProposedAccount(*accountID, session); } } @@ -180,9 +190,10 @@ private: std::shared_ptr const& session, boost::asio::yield_context& yield) const { + static auto constexpr fetchLimit = 200; + boost::json::array snapshots; std::optional rng; - static auto constexpr fetchLimit = 200; for (auto const& internalBook : books) { @@ -190,6 +201,7 @@ private: { if (!rng) rng = sharedPtrBackend_->fetchLedgerRange(); + auto const getOrderBook = [&](auto const& book) { auto const bookBase = getBookBase(book); auto const [offers, _] = @@ -198,18 +210,21 @@ private: // the taker is not really uesed, same issue with // https://github.com/XRPLF/xrpl-dev-portal/issues/1818 auto const takerID = - internalBook.taker ? RPC::accountFromStringStrict(*(internalBook.taker)) : beast::zero; + internalBook.taker ? accountFromStringStrict(*(internalBook.taker)) : beast::zero; auto const orderBook = - RPC::postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield); + postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield); std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshots)); }; + getOrderBook(internalBook.book); + if (internalBook.both) getOrderBook(ripple::reversed(internalBook.book)); } subscriptions_->subBook(internalBook.book, session); + if (internalBook.both) subscriptions_->subBook(ripple::reversed(internalBook.book), session); } @@ -221,6 +236,7 @@ private: tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output) { jv = output.ledger ? *(output.ledger) : boost::json::object(); + if (output.offers) jv.as_object().emplace(JS(offers), *(output.offers)); } @@ -228,48 +244,57 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) { + auto input = Input{}; auto const& jsonObject = jv.as_object(); - Input input; + if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) { input.streams = std::vector(); for (auto const& stream : streams->value().as_array()) input.streams->push_back(stream.as_string().c_str()); } + if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end()) { input.accounts = std::vector(); for (auto const& account : accounts->value().as_array()) input.accounts->push_back(account.as_string().c_str()); } + if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed)); accountsProposed != jsonObject.end()) { input.accountsProposed = std::vector(); for (auto const& account : accountsProposed->value().as_array()) input.accountsProposed->push_back(account.as_string().c_str()); } + if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) { input.books = std::vector(); for (auto const& book : books->value().as_array()) { + auto internalBook = OrderBook{}; auto const& bookObject = book.as_object(); - OrderBook internalBook; + if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end()) internalBook.taker = taker->value().as_string().c_str(); + if (auto const& both = bookObject.find(JS(both)); both != bookObject.end()) internalBook.both = both->value().as_bool(); + if (auto const& snapshot = bookObject.find(JS(snapshot)); snapshot != bookObject.end()) internalBook.snapshot = snapshot->value().as_bool(); - auto const parsedBookMaybe = RPC::parseBook(book.as_object()); + + auto const parsedBookMaybe = parseBook(book.as_object()); internalBook.book = std::get(parsedBookMaybe); input.books->push_back(internalBook); } } + return input; } }; using SubscribeHandler = BaseSubscribeHandler; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/handlers/TransactionEntry.cpp b/src/rpc/handlers/TransactionEntry.cpp index dfb59f0d..66f48958 100644 --- a/src/rpc/handlers/TransactionEntry.cpp +++ b/src/rpc/handlers/TransactionEntry.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,25 +17,22 @@ */ //============================================================================== -#include +#include namespace RPC { -Result -doTransactionEntry(Context const& context) +TransactionEntryHandler::Result +TransactionEntryHandler::process(TransactionEntryHandler::Input input, Context const& ctx) const { - boost::json::object response; - auto v = ledgerInfoFromRequest(context); - if (auto status = std::get_if(&v)) - return *status; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - auto lgrInfo = std::get(v); + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; - ripple::uint256 hash; - if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash)))) - return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"}; - - auto dbResponse = context.backend->fetchTransaction(hash, context.yield); + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const dbRet = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.txHash.c_str()}, ctx.yield); // Note: transaction_entry is meant to only search a specified ledger for // the specified transaction. tx searches the entire range of history. For // rippled, having two separate commands made sense, as tx would use SQLite @@ -45,15 +42,51 @@ doTransactionEntry(Context const& context) // the API for transaction_entry says the method only searches the specified // ledger; we simulate that here by returning not found if the transaction // is in a different ledger than the one specified. - if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq) - return Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}; + if (!dbRet || dbRet->ledgerSequence != lgrInfo.seq) + return Error{Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}}; - auto [txn, meta] = toExpandedJson(*dbResponse); - response[JS(tx_json)] = std::move(txn); - response[JS(metadata)] = std::move(meta); - response[JS(ledger_index)] = lgrInfo.seq; - response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - return response; + auto output = TransactionEntryHandler::Output{}; + auto [txn, meta] = toExpandedJson(*dbRet); + + output.tx = std::move(txn); + output.metadata = std::move(meta); + output.ledgerIndex = lgrInfo.seq; + output.ledgerHash = ripple::strHex(lgrInfo.hash); + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TransactionEntryHandler::Output const& output) +{ + jv = { + {JS(metadata), output.metadata}, + {JS(tx_json), output.tx}, + {JS(ledger_index), output.ledgerIndex}, + {JS(ledger_hash), output.ledgerHash}, + }; +} + +TransactionEntryHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = TransactionEntryHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.txHash = jv.at(JS(tx_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/TransactionEntry.h b/src/rpc/handlers/TransactionEntry.h similarity index 96% rename from src/rpc/ngHandlers/TransactionEntry.h rename to src/rpc/handlers/TransactionEntry.h index 188dd77c..661bbe3d 100644 --- a/src/rpc/ngHandlers/TransactionEntry.h +++ b/src/rpc/handlers/TransactionEntry.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { class TransactionEntryHandler { std::shared_ptr sharedPtrBackend_; @@ -48,7 +48,7 @@ public: std::optional ledgerIndex; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; TransactionEntryHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) @@ -77,4 +77,5 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng + +} // namespace RPC diff --git a/src/rpc/handlers/Tx.cpp b/src/rpc/handlers/Tx.cpp index dabb103d..66f3e20e 100644 --- a/src/rpc/handlers/Tx.cpp +++ b/src/rpc/handlers/Tx.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2022, the clio developers. + Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,84 +17,106 @@ */ //============================================================================== -#include #include +#include namespace RPC { -// { -// transaction: -// } - -Result -doTx(Context const& context) +TxHandler::Result +TxHandler::process(Input input, Context const& ctx) const { - auto request = context.params; - boost::json::object response = {}; - - if (!request.contains(JS(transaction))) - return Status{RippledError::rpcINVALID_PARAMS, "specifyTransaction"}; - - if (!request.at(JS(transaction)).is_string()) - return Status{RippledError::rpcINVALID_PARAMS, "transactionNotString"}; - - ripple::uint256 hash; - if (!hash.parseHex(request.at(JS(transaction)).as_string().c_str())) - return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"}; - - bool binary = false; - if (request.contains(JS(binary))) - { - if (!request.at(JS(binary)).is_bool()) - return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"}; - - binary = request.at(JS(binary)).as_bool(); - } - auto minLedger = getUInt(request, JS(min_ledger)); - auto maxLedger = getUInt(request, JS(max_ledger)); - bool rangeSupplied = minLedger && maxLedger; + constexpr static auto maxLedgerRange = 1000u; + auto const rangeSupplied = input.minLedger && input.maxLedger; if (rangeSupplied) { - if (*minLedger > *maxLedger) - return Status{RippledError::rpcINVALID_LGR_RANGE}; - if (*maxLedger - *minLedger > 1000) - return Status{RippledError::rpcEXCESSIVE_LGR_RANGE}; + if (*input.minLedger > *input.maxLedger) + return Error{Status{RippledError::rpcINVALID_LGR_RANGE}}; + + if (*input.maxLedger - *input.minLedger > maxLedgerRange) + return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}}; } - auto range = context.backend->fetchLedgerRange(); - if (!range) - return Status{RippledError::rpcNOT_READY}; + auto output = TxHandler::Output{}; + auto const dbResponse = + sharedPtrBackend_->fetchTransaction(ripple::uint256{std::string_view(input.transaction)}, ctx.yield); - auto dbResponse = context.backend->fetchTransaction(hash, context.yield); if (!dbResponse) { if (rangeSupplied) { - bool searchedAll = range->maxSequence >= *maxLedger && range->minSequence <= *minLedger; + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const searchedAll = range->maxSequence >= *input.maxLedger && range->minSequence <= *input.minLedger; boost::json::object extra; extra["searched_all"] = searchedAll; - return Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}; + + return Error{Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}}; } - return Status{RippledError::rpcTXN_NOT_FOUND}; + + return Error{Status{RippledError::rpcTXN_NOT_FOUND}}; } - if (!binary) + // clio does not implement 'inLedger' which is a deprecated field + if (!input.binary) { - auto [txn, meta] = toExpandedJson(*dbResponse); - response = txn; - response[JS(meta)] = meta; + auto const [txn, meta] = toExpandedJson(*dbResponse); + output.tx = txn; + output.meta = meta; } else { - response[JS(tx)] = ripple::strHex(dbResponse->transaction); - response[JS(meta)] = ripple::strHex(dbResponse->metadata); - response[JS(hash)] = std::move(request.at(JS(transaction)).as_string()); + output.txStr = ripple::strHex(dbResponse->transaction); + output.metaStr = ripple::strHex(dbResponse->metadata); + output.hash = std::move(input.transaction); } - response[JS(date)] = dbResponse->date; - response[JS(ledger_index)] = dbResponse->ledgerSequence; - return response; + output.date = dbResponse->date; + output.ledgerIndex = dbResponse->ledgerSequence; + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TxHandler::Output const& output) +{ + auto obj = boost::json::object{}; + + if (output.tx) + { + obj = *output.tx; + obj["meta"] = *output.meta; + } + else + { + obj["meta"] = *output.metaStr; + obj["tx"] = *output.txStr; + obj["hash"] = output.hash; + } + + obj["date"] = output.date; + obj["ledger_index"] = output.ledgerIndex; + + jv = std::move(obj); +} + +TxHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = TxHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.transaction = jv.at("transaction").as_string().c_str(); + + if (jsonObject.contains("binary")) + input.binary = jv.at("binary").as_bool(); + + if (jsonObject.contains("min_ledger")) + input.minLedger = jv.at("min_ledger").as_int64(); + + if (jsonObject.contains("max_ledger")) + input.maxLedger = jv.at("max_ledger").as_int64(); + + return input; } } // namespace RPC diff --git a/src/rpc/ngHandlers/Tx.h b/src/rpc/handlers/Tx.h similarity index 96% rename from src/rpc/ngHandlers/Tx.h rename to src/rpc/handlers/Tx.h index 95163aeb..269c99c8 100644 --- a/src/rpc/ngHandlers/Tx.h +++ b/src/rpc/handlers/Tx.h @@ -23,7 +23,7 @@ #include #include -namespace RPCng { +namespace RPC { class TxHandler { std::shared_ptr sharedPtrBackend_; @@ -50,7 +50,7 @@ public: std::optional maxLedger; }; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; TxHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { @@ -79,4 +79,4 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/Unsubscribe.h b/src/rpc/handlers/Unsubscribe.h similarity index 88% rename from src/rpc/ngHandlers/Unsubscribe.h rename to src/rpc/handlers/Unsubscribe.h index ec22dd9b..60a6257d 100644 --- a/src/rpc/ngHandlers/Unsubscribe.h +++ b/src/rpc/handlers/Unsubscribe.h @@ -24,7 +24,7 @@ #include #include -namespace RPCng { +namespace RPC { template class BaseUnsubscribeHandler @@ -48,7 +48,7 @@ public: }; using Output = VoidOutput; - using Result = RPCng::HandlerReturnType; + using Result = HandlerReturnType; BaseUnsubscribeHandler( std::shared_ptr const& sharedPtrBackend, @@ -63,21 +63,21 @@ public: static auto const booksValidator = validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_array()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}}; for (auto const& book : value.as_array()) { if (!book.is_object()) - return Error{ - RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}}; if (book.as_object().contains("both") && !book.as_object().at("both").is_bool()) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "bothNotBool"}}; + return Error{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}}; - auto const parsedBook = RPC::parseBook(book.as_object()); - if (auto const status = std::get_if(&parsedBook)) + auto const parsedBook = parseBook(book.as_object()); + if (auto const status = std::get_if(&parsedBook)) return Error(*status); } + return MaybeError{}; }}; @@ -85,7 +85,8 @@ public: {JS(streams), validation::SubscribeStreamValidator}, {JS(accounts), validation::SubscribeAccountsValidator}, {JS(accounts_proposed), validation::SubscribeAccountsValidator}, - {JS(books), booksValidator}}; + {JS(books), booksValidator}, + }; return rpcSpec; } @@ -136,7 +137,7 @@ private: { for (auto const& account : accounts) { - auto accountID = RPC::accountFromStringStrict(account); + auto const accountID = accountFromStringStrict(account); subscriptions_->unsubAccount(*accountID, session); } } @@ -147,7 +148,7 @@ private: { for (auto const& account : accountsProposed) { - auto accountID = RPC::accountFromStringStrict(account); + auto const accountID = accountFromStringStrict(account); subscriptions_->unsubProposedAccount(*accountID, session); } } @@ -158,6 +159,7 @@ private: for (auto const& orderBook : books) { subscriptions_->unsubBook(orderBook.book, session); + if (orderBook.both) subscriptions_->unsubBook(ripple::reversed(orderBook.book), session); } @@ -166,8 +168,9 @@ private: friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) { + auto input = Input{}; auto const& jsonObject = jv.as_object(); - Input input; + if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) { input.streams = std::vector(); @@ -191,19 +194,22 @@ private: input.books = std::vector(); for (auto const& book : books->value().as_array()) { + auto internalBook = OrderBook{}; auto const& bookObject = book.as_object(); - OrderBook internalBook; + if (auto const& both = bookObject.find(JS(both)); both != bookObject.end()) internalBook.both = both->value().as_bool(); - auto const parsedBookMaybe = RPC::parseBook(book.as_object()); + + auto const parsedBookMaybe = parseBook(book.as_object()); internalBook.book = std::get(parsedBookMaybe); input.books->push_back(internalBook); } } + return input; } }; using UnsubscribeHandler = BaseUnsubscribeHandler; -} // namespace RPCng +} // namespace RPC diff --git a/src/rpc/ngHandlers/AccountChannels.cpp b/src/rpc/ngHandlers/AccountChannels.cpp deleted file mode 100644 index d6550a35..00000000 --- a/src/rpc/ngHandlers/AccountChannels.cpp +++ /dev/null @@ -1,179 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -void -AccountChannelsHandler::addChannel(std::vector& jsonChannels, ripple::SLE const& channelSle) const -{ - ChannelResponse channel; - channel.channelID = ripple::to_string(channelSle.key()); - channel.account = ripple::to_string(channelSle.getAccountID(ripple::sfAccount)); - channel.accountDestination = ripple::to_string(channelSle.getAccountID(ripple::sfDestination)); - channel.amount = channelSle[ripple::sfAmount].getText(); - channel.balance = channelSle[ripple::sfBalance].getText(); - if (publicKeyType(channelSle[ripple::sfPublicKey])) - { - ripple::PublicKey const pk(channelSle[ripple::sfPublicKey]); - channel.publicKey = toBase58(ripple::TokenType::AccountPublic, pk); - channel.publicKeyHex = strHex(pk); - } - channel.settleDelay = channelSle[ripple::sfSettleDelay]; - if (auto const& v = channelSle[~ripple::sfExpiration]) - channel.expiration = *v; - if (auto const& v = channelSle[~ripple::sfCancelAfter]) - channel.cancelAfter = *v; - if (auto const& v = channelSle[~ripple::sfSourceTag]) - channel.sourceTag = *v; - if (auto const& v = channelSle[~ripple::sfDestinationTag]) - channel.destinationTag = *v; - - jsonChannels.push_back(channel); -} - -AccountChannelsHandler::Result -AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - // no need to check the return value, validator check for us - auto const accountID = RPC::accountFromStringStrict(input.account); - - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - auto const destAccountID = input.destinationAccount ? RPC::accountFromStringStrict(input.destinationAccount.value()) - : std::optional{}; - - Output response; - auto const addToResponse = [&](ripple::SLE&& sle) { - if (sle.getType() == ripple::ltPAYCHAN && sle.getAccountID(ripple::sfAccount) == accountID && - (!destAccountID || *destAccountID == sle.getAccountID(ripple::sfDestination))) - { - addChannel(response.channels, sle); - } - return true; - }; - - auto const next = RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - - response.account = input.account; - response.limit = input.limit; - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - - auto const nextMarker = std::get(next); - if (nextMarker.isNonZero()) - response.marker = nextMarker.toString(); - - return response; -} - -AccountChannelsHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountChannelsHandler::Input input; - input.account = jv.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(limit))) - { - input.limit = jv.at(JS(limit)).as_int64(); - } - if (jsonObject.contains(JS(marker))) - { - input.marker = jv.at(JS(marker)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(destination_account))) - { - input.destinationAccount = jv.at(JS(destination_account)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - - return input; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountChannelsHandler::Output const& output) -{ - boost::json::object obj; - obj = { - {JS(account), output.account}, - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(limit), output.limit}, - {JS(channels), output.channels}}; - if (output.marker) - obj[JS(marker)] = output.marker.value(); - jv = obj; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountChannelsHandler::ChannelResponse const& channel) -{ - boost::json::object obj; - obj = { - {JS(channel_id), channel.channelID}, - {JS(account), channel.account}, - {JS(destination_account), channel.accountDestination}, - {JS(amount), channel.amount}, - {JS(balance), channel.balance}, - {JS(settle_delay), channel.settleDelay}}; - if (channel.publicKey) - obj[JS(public_key)] = *(channel.publicKey); - if (channel.publicKeyHex) - obj[JS(public_key_hex)] = *(channel.publicKeyHex); - if (channel.expiration) - obj[JS(expiration)] = *(channel.expiration); - if (channel.cancelAfter) - obj[JS(cancel_after)] = *(channel.cancelAfter); - if (channel.sourceTag) - obj[JS(source_tag)] = *(channel.sourceTag); - if (channel.destinationTag) - obj[JS(destination_tag)] = *(channel.destinationTag); - jv = obj; -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountCurrencies.cpp b/src/rpc/ngHandlers/AccountCurrencies.cpp deleted file mode 100644 index 3de4b7cc..00000000 --- a/src/rpc/ngHandlers/AccountCurrencies.cpp +++ /dev/null @@ -1,112 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { -AccountCurrenciesHandler::Result -AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - auto const accountID = RPC::accountFromStringStrict(input.account); - - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - Output response; - auto const addToResponse = [&](ripple::SLE&& sle) { - if (sle.getType() == ripple::ltRIPPLE_STATE) - { - ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); - auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit); - auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit); - bool const viewLowest = (lowLimit.getIssuer() == accountID); - auto const lineLimit = viewLowest ? lowLimit : highLimit; - auto const lineLimitPeer = !viewLowest ? lowLimit : highLimit; - if (!viewLowest) - balance.negate(); - if (balance < lineLimit) - response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency())); - if ((-balance) < lineLimitPeer) - response.sendCurrencies.insert(ripple::to_string(balance.getCurrency())); - } - return true; - }; - - // traverse all owned nodes, limit->max, marker->empty - RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, - *accountID, - lgrInfo.seq, - std::numeric_limits::max(), - {}, - ctx.yield, - addToResponse); - - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountCurrenciesHandler::Output const& output) -{ - jv = { - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(receive_currencies), output.receiveCurrencies}, - {JS(send_currencies), output.sendCurrencies}}; -} - -AccountCurrenciesHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountCurrenciesHandler::Input input; - input.account = jv.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountInfo.cpp b/src/rpc/ngHandlers/AccountInfo.cpp deleted file mode 100644 index ca54edb9..00000000 --- a/src/rpc/ngHandlers/AccountInfo.cpp +++ /dev/null @@ -1,123 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { -AccountInfoHandler::Result -AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) const -{ - if (!input.account && !input.ident) - return Error{RPC::Status{RPC::RippledError::rpcACT_MALFORMED}}; - - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - auto const accountStr = input.account.value_or(input.ident.value_or("")); - auto const accountID = RPC::accountFromStringStrict(accountStr); - auto const accountKeylet = ripple::keylet::account(*accountID); - auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - ripple::STLedgerEntry const sle{ - ripple::SerialIter{accountLedgerObject->data(), accountLedgerObject->size()}, accountKeylet.key}; - if (!accountKeylet.check(sle)) - return Error{RPC::Status{RPC::RippledError::rpcDB_DESERIALIZATION}}; - // Return SignerList(s) if that is requested. - if (input.signerLists) - { - // We put the SignerList in an array because of an anticipated - // future when we support multiple signer lists on one account. - auto const signersKey = ripple::keylet::signers(*accountID); - // This code will need to be revisited if in the future we - // support multiple SignerLists on one account. - auto const signers = sharedPtrBackend_->fetchLedgerObject(signersKey.key, lgrInfo.seq, ctx.yield); - std::vector signerList; - if (signers) - { - ripple::STLedgerEntry const sleSigners{ - ripple::SerialIter{signers->data(), signers->size()}, signersKey.key}; - if (!signersKey.check(sleSigners)) - return Error{RPC::Status{RPC::RippledError::rpcDB_DESERIALIZATION}}; - - signerList.push_back(sleSigners); - } - return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, signerList); - } - return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle); -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandler::Output const& output) -{ - jv = boost::json::object{ - {JS(account_data), RPC::toJson(output.accountData)}, - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}}; - if (output.signerLists) - { - jv.as_object()[JS(signer_lists)] = boost::json::array(); - for (auto const& signerList : output.signerLists.value()) - jv.as_object()[JS(signer_lists)].as_array().push_back(RPC::toJson(signerList)); - } -} - -AccountInfoHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountInfoHandler::Input input; - if (jsonObject.contains(JS(ident))) - { - input.ident = jsonObject.at(JS(ident)).as_string().c_str(); - } - if (jsonObject.contains(JS(account))) - { - input.account = jsonObject.at(JS(account)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(signer_lists))) - { - input.signerLists = jsonObject.at(JS(signer_lists)).as_bool(); - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountLines.cpp b/src/rpc/ngHandlers/AccountLines.cpp deleted file mode 100644 index b47a8c2e..00000000 --- a/src/rpc/ngHandlers/AccountLines.cpp +++ /dev/null @@ -1,237 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -void -AccountLinesHandler::addLine( - std::vector& lines, - ripple::SLE const& lineSle, - ripple::AccountID const& account, - std::optional const& peerAccount) const -{ - auto const flags = lineSle.getFieldU32(ripple::sfFlags); - auto const lowLimit = lineSle.getFieldAmount(ripple::sfLowLimit); - auto const highLimit = lineSle.getFieldAmount(ripple::sfHighLimit); - auto const lowID = lowLimit.getIssuer(); - auto const highID = highLimit.getIssuer(); - auto const lowQualityIn = lineSle.getFieldU32(ripple::sfLowQualityIn); - auto const lowQualityOut = lineSle.getFieldU32(ripple::sfLowQualityOut); - auto const highQualityIn = lineSle.getFieldU32(ripple::sfHighQualityIn); - auto const highQualityOut = lineSle.getFieldU32(ripple::sfHighQualityOut); - auto balance = lineSle.getFieldAmount(ripple::sfBalance); - - auto const viewLowest = (lowID == account); - auto const lineLimit = viewLowest ? lowLimit : highLimit; - auto const lineLimitPeer = not viewLowest ? lowLimit : highLimit; - auto const lineAccountIDPeer = not viewLowest ? lowID : highID; - auto const lineQualityIn = viewLowest ? lowQualityIn : highQualityIn; - auto const lineQualityOut = viewLowest ? lowQualityOut : highQualityOut; - - if (peerAccount && peerAccount != lineAccountIDPeer) - return; - - if (not viewLowest) - balance.negate(); - - bool const lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); - bool const lineAuthPeer = flags & (not viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); - bool const lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); - bool const lineNoRipplePeer = flags & (not viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); - bool const lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - bool const lineFreezePeer = flags & (not viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - - ripple::STAmount const& saBalance = balance; - ripple::STAmount const& saLimit = lineLimit; - ripple::STAmount const& saLimitPeer = lineLimitPeer; - - LineResponse line; - line.account = ripple::to_string(lineAccountIDPeer); - line.balance = saBalance.getText(); - line.currency = ripple::to_string(saBalance.issue().currency); - line.limit = saLimit.getText(); - line.limitPeer = saLimitPeer.getText(); - line.qualityIn = lineQualityIn; - line.qualityOut = lineQualityOut; - if (lineAuth) - line.authorized = true; - if (lineAuthPeer) - line.peerAuthorized = true; - if (lineFreeze) - line.freeze = true; - if (lineFreezePeer) - line.freezePeer = true; - line.noRipple = lineNoRipple; - line.noRipplePeer = lineNoRipplePeer; - - lines.push_back(line); -} - -AccountLinesHandler::Result -AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const accountID = RPC::accountFromStringStrict(input.account); - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - - if (not accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - auto const peerAccountID = - input.peer ? RPC::accountFromStringStrict(*(input.peer)) : std::optional{}; - - Output response; - response.lines.reserve(input.limit); - - auto const addToResponse = [&](ripple::SLE&& sle) { - if (sle.getType() == ripple::ltRIPPLE_STATE) - { - auto ignore = false; - if (input.ignoreDefault) - { - if (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() == accountID) - { - ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfLowReserve); - } - else - { - ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfHighReserve); - } - } - - if (not ignore) - addLine(response.lines, sle, *accountID, peerAccountID); - } - }; - - auto const next = RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - - response.account = input.account; - response.limit = input.limit; // not documented, - // https://github.com/XRPLF/xrpl-dev-portal/issues/1838 - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - - auto const nextMarker = std::get(next); - if (nextMarker.isNonZero()) - response.marker = nextMarker.toString(); - - return response; -} - -AccountLinesHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountLinesHandler::Input input; - - input.account = jv.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(limit))) - { - input.limit = jv.at(JS(limit)).as_int64(); - } - if (jsonObject.contains(JS(marker))) - { - input.marker = jv.at(JS(marker)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(peer))) - { - input.peer = jv.at(JS(peer)).as_string().c_str(); - } - if (jsonObject.contains(JS(ignore_default))) - { - input.ignoreDefault = jv.at(JS(ignore_default)).as_bool(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - - return input; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountLinesHandler::Output const& output) -{ - auto obj = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(limit), output.limit}, - {JS(lines), output.lines}, - }; - if (output.marker) - obj[JS(marker)] = output.marker.value(); - jv = std::move(obj); -} - -void -tag_invoke( - boost::json::value_from_tag, - boost::json::value& jv, - [[maybe_unused]] AccountLinesHandler::LineResponse const& line) -{ - auto obj = boost::json::object{ - {JS(account), line.account}, - {JS(balance), line.balance}, - {JS(currency), line.currency}, - {JS(limit), line.limit}, - {JS(limit_peer), line.limitPeer}, - {JS(quality_in), line.qualityIn}, - {JS(quality_out), line.qualityOut}, - }; - - obj[JS(no_ripple)] = line.noRipple; - obj[JS(no_ripple_peer)] = line.noRipplePeer; - - if (line.authorized) - obj[JS(authorized)] = *(line.authorized); - if (line.peerAuthorized) - obj[JS(peer_authorized)] = *(line.peerAuthorized); - if (line.freeze) - obj[JS(freeze)] = *(line.freeze); - if (line.freezePeer) - obj[JS(freeze_peer)] = *(line.freezePeer); - - jv = std::move(obj); -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountObjects.cpp b/src/rpc/ngHandlers/AccountObjects.cpp deleted file mode 100644 index c68842bc..00000000 --- a/src/rpc/ngHandlers/AccountObjects.cpp +++ /dev/null @@ -1,130 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -// document does not mention nft_page, we still support it tho -std::unordered_map const AccountObjectsHandler::TYPESMAP{ - {"state", ripple::ltRIPPLE_STATE}, - {"ticket", ripple::ltTICKET}, - {"signer_list", ripple::ltSIGNER_LIST}, - {"payment_channel", ripple::ltPAYCHAN}, - {"offer", ripple::ltOFFER}, - {"escrow", ripple::ltESCROW}, - {"deposit_preauth", ripple::ltDEPOSIT_PREAUTH}, - {"check", ripple::ltCHECK}, - {"nft_page", ripple::ltNFTOKEN_PAGE}, - {"nft_offer", ripple::ltNFTOKEN_OFFER}}; - -AccountObjectsHandler::Result -AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const accountID = RPC::accountFromStringStrict(input.account); - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - Output response; - auto const addToResponse = [&](ripple::SLE&& sle) { - if (!input.type || sle.getType() == *(input.type)) - response.accountObjects.push_back(std::move(sle)); - return true; - }; - - auto const next = RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - - if (auto status = std::get_if(&next)) - return Error{*status}; - - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - response.limit = input.limit; - response.account = input.account; - - auto const& nextMarker = std::get(next); - - if (nextMarker.isNonZero()) - response.marker = nextMarker.toString(); - - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHandler::Output const& output) -{ - boost::json::array objects; - for (auto const& sle : output.accountObjects) - objects.push_back(RPC::toJson(sle)); - - jv = { - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(limit), output.limit}, - {JS(account), output.account}, - {JS(account_objects), objects}}; - - if (output.marker) - jv.as_object()[JS(marker)] = *(output.marker); -} - -AccountObjectsHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountObjectsHandler::Input input; - input.account = jv.at(JS(account)).as_string().c_str(); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - - if (jsonObject.contains(JS(type))) - input.type = AccountObjectsHandler::TYPESMAP.at(jv.at(JS(type)).as_string().c_str()); - - if (jsonObject.contains(JS(limit))) - input.limit = jv.at(JS(limit)).as_int64(); - - if (jsonObject.contains(JS(marker))) - input.marker = jv.at(JS(marker)).as_string().c_str(); - - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountOffers.cpp b/src/rpc/ngHandlers/AccountOffers.cpp deleted file mode 100644 index 025aa030..00000000 --- a/src/rpc/ngHandlers/AccountOffers.cpp +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -void -AccountOffersHandler::addOffer(std::vector& offers, ripple::SLE const& offerSle) const -{ - AccountOffersHandler::Offer offer; - offer.takerPays = offerSle.getFieldAmount(ripple::sfTakerPays); - offer.takerGets = offerSle.getFieldAmount(ripple::sfTakerGets); - - offer.seq = offerSle.getFieldU32(ripple::sfSequence); - offer.flags = offerSle.getFieldU32(ripple::sfFlags); - auto const quality = getQuality(offerSle.getFieldH256(ripple::sfBookDirectory)); - ripple::STAmount const rate = ripple::amountFromQuality(quality); - offer.quality = rate.getText(); - if (offerSle.isFieldPresent(ripple::sfExpiration)) - offer.expiration = offerSle.getFieldU32(ripple::sfExpiration); - offers.push_back(offer); -}; - -AccountOffersHandler::Result -AccountOffersHandler::process(AccountOffersHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - auto const accountID = RPC::accountFromStringStrict(input.account); - - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - Output response; - response.account = ripple::to_string(*accountID); - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - - auto const addToResponse = [&](ripple::SLE&& sle) { - if (sle.getType() == ripple::ltOFFER) - { - addOffer(response.offers, sle); - } - - return true; - }; - - auto const next = RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse); - - if (auto const status = std::get_if(&next)) - return Error{*status}; - - auto const nextMarker = std::get(next); - - if (nextMarker.isNonZero()) - response.marker = nextMarker.toString(); - - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Output const& output) -{ - jv = { - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(account), output.account}, - {JS(offers), boost::json::value_from(output.offers)}}; - if (output.marker) - jv.as_object()[JS(marker)] = *output.marker; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Offer const& offer) -{ - jv = {{JS(seq), offer.seq}, {JS(flags), offer.flags}, {JS(quality), offer.quality}}; - auto& jsonObject = jv.as_object(); - if (offer.expiration) - jsonObject[JS(expiration)] = *offer.expiration; - - auto const convertAmount = [&](const char* field, ripple::STAmount const& amount) { - if (amount.native()) - { - jsonObject[field] = amount.getText(); - } - else - { - jsonObject[field] = { - {JS(currency), ripple::to_string(amount.getCurrency())}, - {JS(issuer), ripple::to_string(amount.getIssuer())}, - {JS(value), amount.getText()}}; - } - }; - convertAmount(JS(taker_pays), offer.takerPays); - convertAmount(JS(taker_gets), offer.takerGets); -} - -AccountOffersHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountOffersHandler::Input input; - input.account = jsonObject.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(limit))) - input.limit = jsonObject.at(JS(limit)).as_int64(); - if (jsonObject.contains(JS(marker))) - input.marker = jsonObject.at(JS(marker)).as_string().c_str(); - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/AccountTx.cpp b/src/rpc/ngHandlers/AccountTx.cpp deleted file mode 100644 index 831a7820..00000000 --- a/src/rpc/ngHandlers/AccountTx.cpp +++ /dev/null @@ -1,210 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -// TODO: this is currently very similar to nft_history but its own copy for time -// being. we should aim to reuse common logic in some way in the future. -AccountTxHandler::Result -AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto [minIndex, maxIndex] = *range; - if (input.ledgerIndexMin) - { - if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin) - { - return Error{RPC::Status{RPC::RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}}; - } - minIndex = *input.ledgerIndexMin; - } - - if (input.ledgerIndexMax) - { - if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax) - return Error{RPC::Status{RPC::RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}}; - maxIndex = *input.ledgerIndexMax; - } - - if (minIndex > maxIndex) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidIndex"}}; - - if (input.ledgerHash || input.ledgerIndex) - { - // rippled does not have this check - if (input.ledgerIndexMax || input.ledgerIndexMin) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}}; - - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - maxIndex = minIndex = std::get(lgrInfoOrStatus).seq; - } - - std::optional cursor; - - // if marker exists - if (input.marker) - { - cursor = {input.marker->ledger, input.marker->seq}; - } - else - { - if (input.forward) - cursor = {minIndex, 0}; - else - cursor = {maxIndex, INT32_MAX}; - } - - static auto constexpr limitDefault = 50; - auto const limit = input.limit.value_or(limitDefault); - auto const accountID = RPC::accountFromStringStrict(input.account); - auto const [txnsAndCursor, timeDiff] = util::timed([&]() { - return sharedPtrBackend_->fetchAccountTransactions(*accountID, limit, input.forward, cursor, ctx.yield); - }); - log_.info() << "db fetch took " << timeDiff << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); - auto const [blobs, retCursor] = txnsAndCursor; - - Output response; - if (retCursor) - response.marker = {retCursor->ledgerSequence, retCursor->transactionIndex}; - - for (auto const& txnPlusMeta : blobs) - { - // over the range - if ((txnPlusMeta.ledgerSequence < minIndex && !input.forward) || - (txnPlusMeta.ledgerSequence > maxIndex && input.forward)) - { - response.marker = std::nullopt; - break; - } - else if (txnPlusMeta.ledgerSequence > maxIndex && !input.forward) - { - log_.debug() << "Skipping over transactions from incomplete ledger"; - continue; - } - - boost::json::object obj; - if (!input.binary) - { - auto [txn, meta] = RPC::toExpandedJson(txnPlusMeta); - obj[JS(meta)] = std::move(meta); - obj[JS(tx)] = std::move(txn); - obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence; - obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; - } - else - { - obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); - obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); - obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; - // only clio has this field - obj[JS(date)] = txnPlusMeta.date; - } - obj[JS(validated)] = true; - - response.transactions.push_back(obj); - } - - response.limit = input.limit; - response.account = ripple::to_string(*accountID); - response.ledgerIndexMin = minIndex; - response.ledgerIndexMax = maxIndex; - - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountTxHandler::Output const& output) -{ - jv = { - {JS(account), output.account}, - {JS(ledger_index_min), output.ledgerIndexMin}, - {JS(ledger_index_max), output.ledgerIndexMax}, - {JS(transactions), output.transactions}, - {JS(validated), output.validated}}; - if (output.marker) - jv.as_object()[JS(marker)] = boost::json::value_from(*(output.marker)); - if (output.limit) - jv.as_object()[JS(limit)] = *(output.limit); -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountTxHandler::Marker const& marker) -{ - jv = {{JS(ledger), marker.ledger}, {JS(seq), marker.seq}}; -} - -AccountTxHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - AccountTxHandler::Input input; - input.account = jsonObject.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_index_min)) && jsonObject.at(JS(ledger_index_min)).as_int64() != -1) - { - input.ledgerIndexMin = jsonObject.at(JS(ledger_index_min)).as_int64(); - } - if (jsonObject.contains(JS(ledger_index_max)) && jsonObject.at(JS(ledger_index_max)).as_int64() != -1) - { - input.ledgerIndexMax = jsonObject.at(JS(ledger_index_max)).as_int64(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(binary))) - { - input.binary = jsonObject.at(JS(binary)).as_bool(); - } - if (jsonObject.contains(JS(forward))) - { - input.forward = jsonObject.at(JS(forward)).as_bool(); - } - if (jsonObject.contains(JS(limit))) - { - input.limit = jsonObject.at(JS(limit)).as_int64(); - } - if (jsonObject.contains(JS(marker))) - { - input.marker = AccountTxHandler::Marker{ - jsonObject.at(JS(marker)).as_object().at(JS(ledger)).as_int64(), - jsonObject.at(JS(marker)).as_object().at(JS(seq)).as_int64()}; - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/BookChanges.cpp b/src/rpc/ngHandlers/BookChanges.cpp deleted file mode 100644 index 056cbe26..00000000 --- a/src/rpc/ngHandlers/BookChanges.cpp +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -BookChangesHandler::Result -BookChangesHandler::process(BookChangesHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const transactions = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield); - - Output response; - response.bookChanges = BookChanges::compute(transactions); - response.ledgerHash = ripple::strHex(lgrInfo.hash); - response.ledgerIndex = lgrInfo.seq; - response.ledgerTime = lgrInfo.closeTime.time_since_epoch().count(); - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChangesHandler::Output const& output) -{ - jv = { - {JS(type), "bookChanges"}, - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(ledger_time), output.ledgerTime}, - {JS(validated), output.validated}, - {JS(changes), boost::json::value_from(output.bookChanges)}}; -} - -BookChangesHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - BookChangesHandler::Input input; - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/BookOffers.cpp b/src/rpc/ngHandlers/BookOffers.cpp deleted file mode 100644 index 2be971d9..00000000 --- a/src/rpc/ngHandlers/BookOffers.cpp +++ /dev/null @@ -1,108 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -BookOffersHandler::Result -BookOffersHandler::process(Input input, Context const& ctx) const -{ - auto bookMaybe = RPC::parseBook(input.paysCurrency, input.paysID, input.getsCurrency, input.getsID); - if (auto const status = std::get_if(&bookMaybe)) - return Error{*status}; - - // check ledger - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const book = std::get(bookMaybe); - auto const bookKey = getBookBase(book); - - // TODO: Add perfomance metrics if needed in future - auto [offers, _] = sharedPtrBackend_->fetchBookOffers(bookKey, lgrInfo.seq, input.limit, ctx.yield); - - BookOffersHandler::Output output; - output.ledgerHash = ripple::strHex(lgrInfo.hash); - output.ledgerIndex = lgrInfo.seq; - output.offers = RPC::postProcessOrderBook( - offers, book, input.taker ? *(input.taker) : beast::zero, *sharedPtrBackend_, lgrInfo.seq, ctx.yield); - - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookOffersHandler::Output const& output) -{ - jv = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(offers), output.offers}, - }; -} - -BookOffersHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - BookOffersHandler::Input input; - auto const& jsonObject = jv.as_object(); - ripple::to_currency(input.getsCurrency, jv.at(JS(taker_gets)).as_object().at(JS(currency)).as_string().c_str()); - ripple::to_currency(input.paysCurrency, jv.at(JS(taker_pays)).as_object().at(JS(currency)).as_string().c_str()); - if (jv.at(JS(taker_gets)).as_object().contains(JS(issuer))) - { - ripple::to_issuer(input.getsID, jv.at(JS(taker_gets)).as_object().at(JS(issuer)).as_string().c_str()); - } - if (jv.at(JS(taker_pays)).as_object().contains(JS(issuer))) - { - ripple::to_issuer(input.paysID, jv.at(JS(taker_pays)).as_object().at(JS(issuer)).as_string().c_str()); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(taker))) - { - input.taker = RPC::accountFromStringStrict(jv.at(JS(taker)).as_string().c_str()); - } - if (jsonObject.contains(JS(limit))) - { - input.limit = jv.at(JS(limit)).as_int64(); - } - - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/GatewayBalances.cpp b/src/rpc/ngHandlers/GatewayBalances.cpp deleted file mode 100644 index 50ee79d1..00000000 --- a/src/rpc/ngHandlers/GatewayBalances.cpp +++ /dev/null @@ -1,222 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -GatewayBalancesHandler::Result -GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context const& ctx) const -{ - // check ledger - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - // check account - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const accountID = RPC::accountFromStringStrict(input.account); - auto const accountLedgerObject = - sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); - if (!accountLedgerObject) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - GatewayBalancesHandler::Output output; - - auto const addToResponse = [&](ripple::SLE&& sle) { - if (sle.getType() == ripple::ltRIPPLE_STATE) - { - ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); - auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit); - auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit); - auto const lowID = lowLimit.getIssuer(); - auto const highID = highLimit.getIssuer(); - auto const viewLowest = (lowLimit.getIssuer() == accountID); - auto const flags = sle.getFieldU32(ripple::sfFlags); - auto const freeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); - if (!viewLowest) - balance.negate(); - - auto const balSign = balance.signum(); - if (balSign == 0) - return true; - - auto const& peer = !viewLowest ? lowID : highID; - - // Here, a negative balance means the cold wallet owes (normal) - // A positive balance means the cold wallet has an asset - // (unusual) - - if (input.hotWallets.count(peer) > 0) - { - // This is a specified hot wallet - output.hotBalances[peer].push_back(-balance); - } - else if (balSign > 0) - { - // This is a gateway asset - output.assets[peer].push_back(balance); - } - else if (freeze) - { - // An obligation the gateway has frozen - output.frozenBalances[peer].push_back(-balance); - } - else - { - // normal negative balance, obligation to customer - auto& bal = output.sums[balance.getCurrency()]; - if (bal == beast::zero) - { - // This is needed to set the currency code correctly - bal = -balance; - } - else - { - try - { - bal -= balance; - } - catch (std::runtime_error const& e) - { - output.overflow = true; - } - } - } - } - return true; - }; - - // traverse all owned nodes, limit->max, marker->empty - auto const ret = RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, - *accountID, - lgrInfo.seq, - std::numeric_limits::max(), - {}, - ctx.yield, - addToResponse); - - if (auto status = std::get_if(&ret)) - return Error{*status}; - - if (not std::all_of(input.hotWallets.begin(), input.hotWallets.end(), [&](auto const& hw) { - return output.hotBalances.contains(hw); - })) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidHotWallet"}}; - - output.accountID = input.account; - output.ledgerHash = ripple::strHex(lgrInfo.hash); - output.ledgerIndex = lgrInfo.seq; - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesHandler::Output const& output) -{ - boost::json::object obj; - if (!output.sums.empty()) - { - boost::json::object obligations; - for (auto const& [k, v] : output.sums) - { - obligations[ripple::to_string(k)] = v.getText(); - } - obj[JS(obligations)] = std::move(obligations); - } - - auto const toJson = [](std::map> const& balances) { - boost::json::object balancesObj; - if (!balances.empty()) - { - for (auto const& [accId, accBalances] : balances) - { - boost::json::array arr; - for (auto const& balance : accBalances) - { - boost::json::object entry; - entry[JS(currency)] = ripple::to_string(balance.issue().currency); - entry[JS(value)] = balance.getText(); - arr.push_back(std::move(entry)); - } - balancesObj[ripple::to_string(accId)] = std::move(arr); - } - } - return balancesObj; - }; - - if (auto balances = toJson(output.hotBalances); balances.size()) - obj[JS(balances)] = balances; - - // we don't have frozen_balances field in the - // document:https://xrpl.org/gateway_balances.html#gateway_balances - if (auto balances = toJson(output.frozenBalances); balances.size()) - obj[JS(frozen_balances)] = balances; - if (auto balances = toJson(output.assets); balances.size()) - obj[JS(assets)] = balances; - obj[JS(account)] = output.accountID; - obj[JS(ledger_index)] = output.ledgerIndex; - obj[JS(ledger_hash)] = output.ledgerHash; - if (output.overflow) - obj["overflow"] = true; - jv = std::move(obj); -} - -GatewayBalancesHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - GatewayBalancesHandler::Input input; - input.account = jv.at(JS(account)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(hotwallet))) - { - if (jsonObject.at(JS(hotwallet)).is_string()) - { - input.hotWallets.insert(*RPC::accountFromStringStrict(jv.at(JS(hotwallet)).as_string().c_str())); - } - else - { - auto const& hotWallets = jv.at(JS(hotwallet)).as_array(); - std::transform( - hotWallets.begin(), - hotWallets.end(), - std::inserter(input.hotWallets, input.hotWallets.begin()), - [](auto const& hotWallet) { return *RPC::accountFromStringStrict(hotWallet.as_string().c_str()); }); - } - } - return input; -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/Ledger.cpp b/src/rpc/ngHandlers/Ledger.cpp deleted file mode 100644 index 255dcfa5..00000000 --- a/src/rpc/ngHandlers/Ledger.cpp +++ /dev/null @@ -1,174 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { -LedgerHandler::Result -LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - Output output; - if (input.binary) - { - output.header[JS(ledger_data)] = ripple::strHex(RPC::ledgerInfoToBlob(lgrInfo)); - } - else - { - output.header[JS(accepted)] = true; - output.header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); - output.header[JS(close_flags)] = lgrInfo.closeFlags; - output.header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); - output.header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); - output.header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); - output.header[JS(closed)] = true; - output.header[JS(hash)] = ripple::strHex(lgrInfo.hash); - output.header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - output.header[JS(ledger_index)] = std::to_string(lgrInfo.seq); - output.header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); - output.header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); - output.header[JS(seqNum)] = std::to_string(lgrInfo.seq); - output.header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); - output.header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); - output.header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); - } - output.header[JS(closed)] = true; - - if (input.transactions) - { - output.header[JS(transactions)] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonTxs = output.header.at(JS(transactions)).as_array(); - if (input.expand) - { - auto txns = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield); - - std::transform( - std::move_iterator(txns.begin()), - std::move_iterator(txns.end()), - std::back_inserter(jsonTxs), - [&](auto obj) { - boost::json::object entry; - if (!input.binary) - { - auto [txn, meta] = RPC::toExpandedJson(obj); - entry = std::move(txn); - entry[JS(metaData)] = std::move(meta); - } - else - { - entry[JS(tx_blob)] = ripple::strHex(obj.transaction); - entry[JS(meta)] = ripple::strHex(obj.metadata); - } - return entry; - }); - } - else - { - auto hashes = sharedPtrBackend_->fetchAllTransactionHashesInLedger(lgrInfo.seq, ctx.yield); - std::transform( - std::move_iterator(hashes.begin()), - std::move_iterator(hashes.end()), - std::back_inserter(jsonTxs), - [](auto hash) { return boost::json::string(ripple::strHex(hash)); }); - } - } - - if (input.diff) - { - output.header["diff"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonDiff = output.header.at("diff").as_array(); - auto diff = sharedPtrBackend_->fetchLedgerDiff(lgrInfo.seq, ctx.yield); - for (auto const& obj : diff) - { - boost::json::object entry; - entry["object_id"] = ripple::strHex(obj.key); - if (input.binary) - { - entry["object"] = ripple::strHex(obj.blob); - } - else if (obj.blob.size()) - { - ripple::STLedgerEntry const sle{ripple::SerialIter{obj.blob.data(), obj.blob.size()}, obj.key}; - entry["object"] = RPC::toJson(sle); - } - else - { - entry["object"] = ""; - } - - jsonDiff.push_back(std::move(entry)); - } - } - - output.ledgerHash = ripple::strHex(lgrInfo.hash); - output.ledgerIndex = lgrInfo.seq; - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerHandler::Output const& output) -{ - jv = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(ledger), output.header}, - }; -} - -LedgerHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - LedgerHandler::Input input; - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - - if (jsonObject.contains(JS(transactions))) - input.transactions = jv.at(JS(transactions)).as_bool(); - - if (jsonObject.contains(JS(binary))) - input.binary = jv.at(JS(binary)).as_bool(); - - if (jsonObject.contains(JS(expand))) - input.expand = jv.at(JS(expand)).as_bool(); - - if (jsonObject.contains("diff")) - input.diff = jv.at("diff").as_bool(); - - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/LedgerData.cpp b/src/rpc/ngHandlers/LedgerData.cpp deleted file mode 100644 index c41d9e68..00000000 --- a/src/rpc/ngHandlers/LedgerData.cpp +++ /dev/null @@ -1,221 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -LedgerDataHandler::Result -LedgerDataHandler::process(Input input, Context const& ctx) const -{ - // marker must be int if outOfOrder is true - if (input.outOfOrder && input.marker) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "outOfOrderMarkerNotInt"}}; - if (!input.outOfOrder && input.diffMarker) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "markerNotString"}}; - - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - - Output output; - // no marker -> first call, return header information - auto header = boost::json::object(); - if ((!input.marker) && (!input.diffMarker)) - { - if (input.binary) - { - header[JS(ledger_data)] = ripple::strHex(RPC::ledgerInfoToBlob(lgrInfo)); - } - else - { - header[JS(accepted)] = true; - header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); - header[JS(close_flags)] = lgrInfo.closeFlags; - header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); - header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); - header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); - header[JS(hash)] = ripple::strHex(lgrInfo.hash); - header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - header[JS(ledger_index)] = std::to_string(lgrInfo.seq); - header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); - header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); - header[JS(seqNum)] = std::to_string(lgrInfo.seq); - header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); - header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); - header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); - } - header[JS(closed)] = true; - output.header = std::move(header); - } - else - { - if (input.marker && !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield)) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}}; - } - - output.ledgerHash = ripple::strHex(lgrInfo.hash); - output.ledgerIndex = lgrInfo.seq; - - auto const start = std::chrono::system_clock::now(); - std::vector results; - if (input.diffMarker) - { - // keep the same logic as previous implementation - auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield); - std::vector keys; - for (auto& [key, object] : diff) - { - if (!object.size()) - keys.push_back(std::move(key)); - } - auto objs = sharedPtrBackend_->fetchLedgerObjects(keys, lgrInfo.seq, ctx.yield); - for (size_t i = 0; i < objs.size(); ++i) - { - auto& obj = objs[i]; - if (obj.size()) - results.push_back({std::move(keys[i]), std::move(obj)}); - } - if (*(input.diffMarker) > lgrInfo.seq) - output.diffMarker = *(input.diffMarker) - 1; - } - else - { - // limit's limitation is different based on binary or json - // framework can not handler the check right now, adjust the value here - auto const limit = - std::min(input.limit, input.binary ? LedgerDataHandler::LIMITBINARY : LedgerDataHandler::LIMITJSON); - auto page = sharedPtrBackend_->fetchLedgerPage(input.marker, lgrInfo.seq, limit, input.outOfOrder, ctx.yield); - results = std::move(page.objects); - if (page.cursor) - output.marker = ripple::strHex(*(page.cursor)); - else if (input.outOfOrder) - output.diffMarker = sharedPtrBackend_->fetchLedgerRange()->maxSequence; - } - auto const end = std::chrono::system_clock::now(); - - log_.debug() << "Number of results = " << results.size() << " fetched in " - << std::chrono::duration_cast(end - start).count() << " microseconds"; - - output.states.reserve(results.size()); - for (auto const& [key, object] : results) - { - ripple::STLedgerEntry const sle{ripple::SerialIter{object.data(), object.size()}, key}; - if (input.binary) - { - boost::json::object entry; - entry[JS(data)] = ripple::serializeHex(sle); - entry[JS(index)] = ripple::to_string(sle.key()); - output.states.push_back(std::move(entry)); - } - else - { - output.states.push_back(RPC::toJson(sle)); - } - } - if (input.outOfOrder) - output.cacheFull = sharedPtrBackend_->cache().isFull(); - - auto const end2 = std::chrono::system_clock::now(); - log_.debug() << "Number of results = " << results.size() << " serialized in " - << std::chrono::duration_cast(end2 - end).count() << " microseconds"; - - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerDataHandler::Output const& output) -{ - auto obj = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(state), output.states}, - }; - if (output.header) - obj[JS(ledger)] = *(output.header); - if (output.cacheFull) - obj["cache_full"] = *(output.cacheFull); - - if (output.diffMarker) - obj[JS(marker)] = *(output.diffMarker); - else if (output.marker) - obj[JS(marker)] = *(output.marker); - - jv = std::move(obj); -} - -LedgerDataHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - LedgerDataHandler::Input input; - auto const& jsonObject = jv.as_object(); - if (jsonObject.contains(JS(binary))) - { - input.binary = jsonObject.at(JS(binary)).as_bool(); - input.limit = input.binary ? LedgerDataHandler::LIMITBINARY : LedgerDataHandler::LIMITJSON; - } - if (jsonObject.contains(JS(limit))) - { - input.limit = jsonObject.at(JS(limit)).as_int64(); - } - if (jsonObject.contains("out_of_order")) - { - input.outOfOrder = jsonObject.at("out_of_order").as_bool(); - } - if (jsonObject.contains("marker")) - { - if (jsonObject.at("marker").is_string()) - { - input.marker = ripple::uint256{jsonObject.at("marker").as_string().c_str()}; - } - else - { - input.diffMarker = jsonObject.at("marker").as_int64(); - } - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/LedgerEntry.cpp b/src/rpc/ngHandlers/LedgerEntry.cpp deleted file mode 100644 index 173fbb23..00000000 --- a/src/rpc/ngHandlers/LedgerEntry.cpp +++ /dev/null @@ -1,234 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { -LedgerEntryHandler::Result -LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) const -{ - ripple::uint256 key; - if (input.index) - { - key = ripple::uint256{std::string_view(*(input.index))}; - } - else if (input.accountRoot) - { - key = ripple::keylet::account(*ripple::parseBase58(*(input.accountRoot))).key; - } - else if (input.directory) - { - auto const keyOrStatus = composeKeyFromDirectory(*input.directory); - if (auto const status = std::get_if(&keyOrStatus)) - return Error{*status}; - key = std::get(keyOrStatus); - } - else if (input.offer) - { - auto const id = ripple::parseBase58(input.offer->at(JS(account)).as_string().c_str()); - key = ripple::keylet::offer(*id, boost::json::value_to(input.offer->at(JS(seq)))).key; - } - else if (input.rippleStateAccount) - { - auto const id1 = ripple::parseBase58( - input.rippleStateAccount->at(JS(accounts)).as_array().at(0).as_string().c_str()); - auto const id2 = ripple::parseBase58( - input.rippleStateAccount->at(JS(accounts)).as_array().at(1).as_string().c_str()); - auto const currency = ripple::to_currency(input.rippleStateAccount->at(JS(currency)).as_string().c_str()); - key = ripple::keylet::line(*id1, *id2, currency).key; - } - else if (input.escrow) - { - auto const id = ripple::parseBase58(input.escrow->at(JS(owner)).as_string().c_str()); - key = ripple::keylet::escrow(*id, input.escrow->at(JS(seq)).as_int64()).key; - } - else if (input.depositPreauth) - { - auto const owner = - ripple::parseBase58(input.depositPreauth->at(JS(owner)).as_string().c_str()); - auto const authorized = - ripple::parseBase58(input.depositPreauth->at(JS(authorized)).as_string().c_str()); - key = ripple::keylet::depositPreauth(*owner, *authorized).key; - } - else if (input.ticket) - { - auto const id = ripple::parseBase58(input.ticket->at(JS(account)).as_string().c_str()); - key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64()); - } - else - { - // Must specify 1 of the following fields to indicate what type - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "unknownOption"}}; - } - - // check ledger exists - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield); - if (!ledgerObject || ledgerObject->size() == 0) - return Error{RPC::Status{"entryNotFound"}}; - - ripple::STLedgerEntry const sle{ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key}; - if (input.expectedType != ripple::ltANY && sle.getType() != input.expectedType) - return Error{RPC::Status{"unexpectedLedgerType"}}; - - LedgerEntryHandler::Output output; - output.index = ripple::strHex(key); - output.ledgerIndex = lgrInfo.seq; - output.ledgerHash = ripple::strHex(lgrInfo.hash); - if (input.binary) - { - output.nodeBinary = ripple::strHex(*ledgerObject); - } - else - { - output.node = RPC::toJson(sle); - } - return output; -} - -std::variant -LedgerEntryHandler::composeKeyFromDirectory(boost::json::object const& directory) const noexcept -{ - // can not specify both dir_root and owner. - if (directory.contains(JS(dir_root)) && directory.contains(JS(owner))) - return RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "mayNotSpecifyBothDirRootAndOwner"}; - // at least one should availiable - if (!(directory.contains(JS(dir_root)) || directory.contains(JS(owner)))) - return RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"}; - - uint64_t const subIndex = - directory.contains(JS(sub_index)) ? boost::json::value_to(directory.at(JS(sub_index))) : 0; - - if (directory.contains(JS(dir_root))) - { - ripple::uint256 const uDirRoot{directory.at(JS(dir_root)).as_string().c_str()}; - return ripple::keylet::page(uDirRoot, subIndex).key; - } - - auto const ownerID = ripple::parseBase58(directory.at(JS(owner)).as_string().c_str()); - return ripple::keylet::page(ripple::keylet::ownerDir(*ownerID), subIndex).key; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerEntryHandler::Output const& output) -{ - auto object = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, - {JS(ledger_index), output.ledgerIndex}, - {JS(validated), output.validated}, - {JS(index), output.index}}; - if (output.nodeBinary) - { - object[JS(node_binary)] = *(output.nodeBinary); - } - else - { - object[JS(node)] = *(output.node); - } - jv = std::move(object); -} - -LedgerEntryHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - LedgerEntryHandler::Input input; - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(binary))) - { - input.binary = jv.at(JS(binary)).as_bool(); - } - // check all the protential index - static auto const indexFieldTypeMap = std::unordered_map{ - {JS(index), ripple::ltANY}, - {JS(directory), ripple::ltDIR_NODE}, - {JS(offer), ripple::ltOFFER}, - {JS(check), ripple::ltCHECK}, - {JS(escrow), ripple::ltESCROW}, - {JS(payment_channel), ripple::ltPAYCHAN}, - {JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH}, - {JS(ticket), ripple::ltTICKET}}; - - auto const indexFieldType = - std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) { - auto const& [field, _] = pair; - return jsonObject.contains(field) && jsonObject.at(field).is_string(); - }); - if (indexFieldType != indexFieldTypeMap.end()) - { - input.index = jv.at(indexFieldType->first).as_string().c_str(); - input.expectedType = indexFieldType->second; - } - // check if request for account root - else if (jsonObject.contains(JS(account_root))) - { - input.accountRoot = jv.at(JS(account_root)).as_string().c_str(); - } - // no need to check if_object again, validator only allows string or object - else if (jsonObject.contains(JS(directory))) - { - input.directory = jv.at(JS(directory)).as_object(); - } - else if (jsonObject.contains(JS(offer))) - { - input.offer = jv.at(JS(offer)).as_object(); - } - else if (jsonObject.contains(JS(ripple_state))) - { - input.rippleStateAccount = jv.at(JS(ripple_state)).as_object(); - } - else if (jsonObject.contains(JS(escrow))) - { - input.escrow = jv.at(JS(escrow)).as_object(); - } - else if (jsonObject.contains(JS(deposit_preauth))) - { - input.depositPreauth = jv.at(JS(deposit_preauth)).as_object(); - } - else if (jsonObject.contains(JS(ticket))) - { - input.ticket = jv.at(JS(ticket)).as_object(); - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/NFTHistory.cpp b/src/rpc/ngHandlers/NFTHistory.cpp deleted file mode 100644 index 242f7d8a..00000000 --- a/src/rpc/ngHandlers/NFTHistory.cpp +++ /dev/null @@ -1,209 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -// TODO: this is currently very similar to account_tx but its own copy for time -// being. we should aim to reuse common logic in some way in the future. -NFTHistoryHandler::Result -NFTHistoryHandler::process(NFTHistoryHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto [minIndex, maxIndex] = *range; - if (input.ledgerIndexMin) - { - if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin) - { - return Error{RPC::Status{RPC::RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}}; - } - minIndex = *input.ledgerIndexMin; - } - - if (input.ledgerIndexMax) - { - if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax) - return Error{RPC::Status{RPC::RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}}; - maxIndex = *input.ledgerIndexMax; - } - - if (minIndex > maxIndex) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidIndex"}}; - - if (input.ledgerHash || input.ledgerIndex) - { - // rippled does not have this check - if (input.ledgerIndexMax || input.ledgerIndexMin) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}}; - - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - maxIndex = minIndex = std::get(lgrInfoOrStatus).seq; - } - - std::optional cursor; - - // if marker exists - if (input.marker) - { - cursor = {input.marker->ledger, input.marker->seq}; - } - else - { - if (input.forward) - cursor = {minIndex, 0}; - else - cursor = {maxIndex, INT32_MAX}; - } - - static auto constexpr limitDefault = 50; - auto const limit = input.limit.value_or(limitDefault); - auto const tokenID = ripple::uint256{input.nftID.c_str()}; - auto const [txnsAndCursor, timeDiff] = util::timed( - [&]() { return sharedPtrBackend_->fetchNFTTransactions(tokenID, limit, input.forward, cursor, ctx.yield); }); - log_.info() << "db fetch took " << timeDiff << " milliseconds - num blobs = " << txnsAndCursor.txns.size(); - auto const [blobs, retCursor] = txnsAndCursor; - - Output response; - if (retCursor) - response.marker = {retCursor->ledgerSequence, retCursor->transactionIndex}; - - for (auto const& txnPlusMeta : blobs) - { - // over the range - if ((txnPlusMeta.ledgerSequence < minIndex && !input.forward) || - (txnPlusMeta.ledgerSequence > maxIndex && input.forward)) - { - response.marker = std::nullopt; - break; - } - else if (txnPlusMeta.ledgerSequence > maxIndex && !input.forward) - { - log_.debug() << "Skipping over transactions from incomplete ledger"; - continue; - } - - boost::json::object obj; - if (!input.binary) - { - auto [txn, meta] = RPC::toExpandedJson(txnPlusMeta); - obj[JS(meta)] = std::move(meta); - obj[JS(tx)] = std::move(txn); - obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence; - obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; - } - else - { - obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); - obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); - obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; - // only clio has this field - obj[JS(date)] = txnPlusMeta.date; - } - obj[JS(validated)] = true; - - response.transactions.push_back(obj); - } - - response.limit = input.limit; - response.nftID = ripple::to_string(tokenID); - response.ledgerIndexMin = minIndex; - response.ledgerIndexMax = maxIndex; - - return response; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTHistoryHandler::Output const& output) -{ - jv = { - {JS(nft_id), output.nftID}, - {JS(ledger_index_min), output.ledgerIndexMin}, - {JS(ledger_index_max), output.ledgerIndexMax}, - {JS(transactions), output.transactions}, - {JS(validated), output.validated}}; - if (output.marker) - jv.as_object()[JS(marker)] = boost::json::value_from(*(output.marker)); - if (output.limit) - jv.as_object()[JS(limit)] = *(output.limit); -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTHistoryHandler::Marker const& marker) -{ - jv = {{JS(ledger), marker.ledger}, {JS(seq), marker.seq}}; -} - -NFTHistoryHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - NFTHistoryHandler::Input input; - input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_index_min)) && jsonObject.at(JS(ledger_index_min)).as_int64() != -1) - { - input.ledgerIndexMin = jsonObject.at(JS(ledger_index_min)).as_int64(); - } - if (jsonObject.contains(JS(ledger_index_max)) && jsonObject.at(JS(ledger_index_max)).as_int64() != -1) - { - input.ledgerIndexMax = jsonObject.at(JS(ledger_index_max)).as_int64(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - if (jsonObject.contains(JS(binary))) - { - input.binary = jsonObject.at(JS(binary)).as_bool(); - } - if (jsonObject.contains(JS(forward))) - { - input.forward = jsonObject.at(JS(forward)).as_bool(); - } - if (jsonObject.contains(JS(limit))) - { - input.limit = jsonObject.at(JS(limit)).as_int64(); - } - if (jsonObject.contains(JS(marker))) - { - input.marker = NFTHistoryHandler::Marker{ - jsonObject.at(JS(marker)).as_object().at(JS(ledger)).as_int64(), - jsonObject.at(JS(marker)).as_object().at(JS(seq)).as_int64()}; - } - return input; -} - -} // namespace RPCng diff --git a/src/rpc/ngHandlers/NFTInfo.cpp b/src/rpc/ngHandlers/NFTInfo.cpp deleted file mode 100644 index 6f40efb3..00000000 --- a/src/rpc/ngHandlers/NFTInfo.cpp +++ /dev/null @@ -1,115 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -using namespace ripple; -using namespace ::RPC; - -namespace RPCng { - -NFTInfoHandler::Result -NFTInfoHandler::process(NFTInfoHandler::Input input, Context const& ctx) const -{ - auto const tokenID = ripple::uint256{input.nftID.c_str()}; - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - if (auto const status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const maybeNft = sharedPtrBackend_->fetchNFT(tokenID, lgrInfo.seq, ctx.yield); - if (not maybeNft.has_value()) - return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"}}; - - auto const& nft = *maybeNft; - auto output = NFTInfoHandler::Output{}; - - output.nftID = strHex(nft.tokenID); - output.ledgerIndex = nft.ledgerSequence; - output.owner = toBase58(nft.owner); - output.isBurned = nft.isBurned; - output.flags = nft::getFlags(nft.tokenID); - output.transferFee = nft::getTransferFee(nft.tokenID); - output.issuer = toBase58(nft::getIssuer(nft.tokenID)); - output.taxon = nft::toUInt32(nft::getTaxon(nft.tokenID)); - output.serial = nft::getSerial(nft.tokenID); - - if (not nft.isBurned) - output.uri = strHex(nft.uri); - - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTInfoHandler::Output const& output) -{ - // TODO: use JStrings when they become available - auto object = boost::json::object{ - {JS(nft_id), output.nftID}, - {JS(ledger_index), output.ledgerIndex}, - {JS(owner), output.owner}, - {"is_burned", output.isBurned}, - {JS(flags), output.flags}, - {"transfer_fee", output.transferFee}, - {JS(issuer), output.issuer}, - {"nft_taxon", output.taxon}, - {JS(nft_serial), output.serial}, - {JS(validated), output.validated}, - }; - - if (output.uri) - object[JS(uri)] = *(output.uri); - - jv = std::move(object); -} - -NFTInfoHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - NFTInfoHandler::Input input; - - input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); - - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - - return input; -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/NoRippleCheck.cpp b/src/rpc/ngHandlers/NoRippleCheck.cpp deleted file mode 100644 index 64b203e5..00000000 --- a/src/rpc/ngHandlers/NoRippleCheck.cpp +++ /dev/null @@ -1,191 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -NoRippleCheckHandler::Result -NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const accountID = RPC::accountFromStringStrict(input.account); - auto const keylet = ripple::keylet::account(*accountID).key; - auto const accountObj = sharedPtrBackend_->fetchLedgerObject(keylet, lgrInfo.seq, ctx.yield); - if (!accountObj) - return Error{RPC::Status{RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; - - ripple::SerialIter it{accountObj->data(), accountObj->size()}; - ripple::SLE sle{it, keylet}; - auto accountSeq = sle.getFieldU32(ripple::sfSequence); - - bool const bDefaultRipple = sle.getFieldU32(ripple::sfFlags) & ripple::lsfDefaultRipple; - - auto const fees = input.transactions ? sharedPtrBackend_->fetchFees(lgrInfo.seq, ctx.yield) : std::nullopt; - - auto output = NoRippleCheckHandler::Output(); - if (input.transactions) - output.transactions.emplace(boost::json::array()); - - auto const getBaseTx = [&](ripple::AccountID const& accountID, std::uint32_t accountSeq) { - boost::json::object tx; - tx[JS(Sequence)] = accountSeq; - tx[JS(Account)] = ripple::toBase58(accountID); - tx[JS(Fee)] = RPC::toBoostJson(fees->units.jsonClipped()); - return tx; - }; - - if (bDefaultRipple && !input.roleGateway) - { - output.problems.push_back( - "You appear to have set your default ripple flag even though " - "you " - "are not a gateway. This is not recommended unless you are " - "experimenting"); - } - else if (input.roleGateway && !bDefaultRipple) - { - output.problems.push_back("You should immediately set your default ripple flag"); - if (input.transactions) - { - auto tx = getBaseTx(*accountID, accountSeq++); - tx[JS(TransactionType)] = "AccountSet"; - tx[JS(SetFlag)] = ripple::asfDefaultRipple; - output.transactions->push_back(tx); - } - } - auto limit = input.limit; - RPC::ngTraverseOwnedNodes( - *sharedPtrBackend_, - *accountID, - lgrInfo.seq, - std::numeric_limits::max(), - {}, - ctx.yield, - [&](ripple::SLE&& ownedItem) { - // don't push to result if limit is reached - if (limit != 0 && ownedItem.getType() == ripple::ltRIPPLE_STATE) - { - bool const bLow = accountID == ownedItem.getFieldAmount(ripple::sfLowLimit).getIssuer(); - - bool const bNoRipple = - ownedItem.getFieldU32(ripple::sfFlags) & (bLow ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); - - std::string problem; - bool needFix = false; - if (bNoRipple && input.roleGateway) - { - problem = "You should clear the no ripple flag on your "; - needFix = true; - } - else if (!bNoRipple && !input.roleGateway) - { - problem = - "You should probably set the no ripple flag on " - "your "; - needFix = true; - } - if (needFix) - { - limit--; - ripple::AccountID peer = - ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit).getIssuer(); - ripple::STAmount peerLimit = - ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); - problem += fmt::format( - "{} line to {}", to_string(peerLimit.getCurrency()), to_string(peerLimit.getIssuer())); - output.problems.emplace_back(problem); - if (input.transactions) - { - ripple::STAmount limitAmount( - ownedItem.getFieldAmount(bLow ? ripple::sfLowLimit : ripple::sfHighLimit)); - limitAmount.setIssuer(peer); - auto tx = getBaseTx(*accountID, accountSeq++); - tx[JS(TransactionType)] = "TrustSet"; - tx[JS(LimitAmount)] = RPC::toBoostJson(limitAmount.getJson(ripple::JsonOptions::none)); - tx[JS(Flags)] = bNoRipple ? ripple::tfClearNoRipple : ripple::tfSetNoRipple; - output.transactions->push_back(tx); - } - } - } - return true; - }); - - output.ledgerIndex = lgrInfo.seq; - output.ledgerHash = ripple::strHex(lgrInfo.hash); - return output; -} - -NoRippleCheckHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - NoRippleCheckHandler::Input input; - input.account = jsonObject.at(JS(account)).as_string().c_str(); - input.roleGateway = jsonObject.at(JS(role)).as_string() == "gateway"; - if (jsonObject.contains(JS(limit))) - { - input.limit = jsonObject.at(JS(limit)).as_int64(); - } - if (jsonObject.contains(JS(transactions))) - { - input.transactions = jsonObject.at(JS(transactions)).as_bool(); - } - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); - } - } - - return input; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NoRippleCheckHandler::Output const& output) -{ - auto obj = boost::json::object{ - {JS(ledger_hash), output.ledgerHash}, {JS(ledger_index), output.ledgerIndex}, {"problems", output.problems}}; - if (output.transactions) - { - obj.emplace(JS(transactions), *(output.transactions)); - } - jv = std::move(obj); -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/TransactionEntry.cpp b/src/rpc/ngHandlers/TransactionEntry.cpp deleted file mode 100644 index 6588f5ce..00000000 --- a/src/rpc/ngHandlers/TransactionEntry.cpp +++ /dev/null @@ -1,92 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -TransactionEntryHandler::Result -TransactionEntryHandler::process(TransactionEntryHandler::Input input, Context const& ctx) const -{ - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); - - if (auto status = std::get_if(&lgrInfoOrStatus)) - return Error{*status}; - - auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const dbRet = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.txHash.c_str()}, ctx.yield); - // Note: transaction_entry is meant to only search a specified ledger for - // the specified transaction. tx searches the entire range of history. For - // rippled, having two separate commands made sense, as tx would use SQLite - // and transaction_entry used the nodestore. For clio though, there is no - // difference between the implementation of these two, as clio only stores - // transactions in a transactions table, where the key is the hash. However, - // the API for transaction_entry says the method only searches the specified - // ledger; we simulate that here by returning not found if the transaction - // is in a different ledger than the one specified. - if (!dbRet || dbRet->ledgerSequence != lgrInfo.seq) - return Error{RPC::Status{RPC::RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}}; - - auto [txn, meta] = RPC::toExpandedJson(*dbRet); - TransactionEntryHandler::Output output; - output.tx = std::move(txn); - output.metadata = std::move(meta); - output.ledgerIndex = lgrInfo.seq; - output.ledgerHash = ripple::strHex(lgrInfo.hash); - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TransactionEntryHandler::Output const& output) -{ - jv = { - {JS(metadata), output.metadata}, - {JS(tx_json), output.tx}, - {JS(ledger_index), output.ledgerIndex}, - {JS(ledger_hash), output.ledgerHash}, - }; -} - -TransactionEntryHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - - TransactionEntryHandler::Input input; - input.txHash = jv.at(JS(tx_hash)).as_string().c_str(); - if (jsonObject.contains(JS(ledger_hash))) - { - input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); - } - if (jsonObject.contains(JS(ledger_index))) - { - if (!jsonObject.at(JS(ledger_index)).is_string()) - { - input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); - } - else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") - { - input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); - } - } - return input; -} -} // namespace RPCng diff --git a/src/rpc/ngHandlers/Tx.cpp b/src/rpc/ngHandlers/Tx.cpp deleted file mode 100644 index c6c77324..00000000 --- a/src/rpc/ngHandlers/Tx.cpp +++ /dev/null @@ -1,115 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. - - Permission to use, copy, modify, and 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 - -namespace RPCng { - -TxHandler::Result -TxHandler::process(Input input, Context const& ctx) const -{ - constexpr static auto maxLedgerRange = 1000u; - auto const rangeSupplied = input.minLedger && input.maxLedger; - - if (rangeSupplied) - { - if (*input.minLedger > *input.maxLedger) - return Error{RPC::Status{RPC::RippledError::rpcINVALID_LGR_RANGE}}; - - if (*input.maxLedger - *input.minLedger > maxLedgerRange) - return Error{RPC::Status{RPC::RippledError::rpcEXCESSIVE_LGR_RANGE}}; - } - TxHandler::Output output; - auto const dbResponse = - sharedPtrBackend_->fetchTransaction(ripple::uint256{std::string_view(input.transaction)}, ctx.yield); - if (!dbResponse) - { - if (rangeSupplied) - { - auto const range = sharedPtrBackend_->fetchLedgerRange(); - auto const searchedAll = range->maxSequence >= *input.maxLedger && range->minSequence <= *input.minLedger; - boost::json::object extra; - extra["searched_all"] = searchedAll; - return Error{RPC::Status{RPC::RippledError::rpcTXN_NOT_FOUND, std::move(extra)}}; - } - return Error{RPC::Status{RPC::RippledError::rpcTXN_NOT_FOUND}}; - } - - // clio does not implement 'inLedger' which is a deprecated field - if (!input.binary) - { - auto const [txn, meta] = RPC::toExpandedJson(*dbResponse); - output.tx = txn; - output.meta = meta; - } - else - { - output.txStr = ripple::strHex(dbResponse->transaction); - output.metaStr = ripple::strHex(dbResponse->metadata); - output.hash = std::move(input.transaction); - } - - output.date = dbResponse->date; - output.ledgerIndex = dbResponse->ledgerSequence; - return output; -} - -void -tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TxHandler::Output const& output) -{ - auto obj = boost::json::object{}; - if (output.tx) - { - obj = *output.tx; - obj["meta"] = *output.meta; - } - else - { - obj["meta"] = *output.metaStr; - obj["tx"] = *output.txStr; - obj["hash"] = output.hash; - } - obj["date"] = output.date; - obj["ledger_index"] = output.ledgerIndex; - jv = std::move(obj); -} - -TxHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - TxHandler::Input input; - auto const& jsonObject = jv.as_object(); - input.transaction = jv.at("transaction").as_string().c_str(); - if (jsonObject.contains("binary")) - { - input.binary = jv.at("binary").as_bool(); - } - if (jsonObject.contains("min_ledger")) - { - input.minLedger = jv.at("min_ledger").as_int64(); - } - if (jsonObject.contains("max_ledger")) - { - input.maxLedger = jv.at("max_ledger").as_int64(); - } - return input; -} - -} // namespace RPCng diff --git a/src/subscriptions/SubscriptionManager.cpp b/src/subscriptions/SubscriptionManager.cpp index c7d464d6..7ccb364b 100644 --- a/src/subscriptions/SubscriptionManager.cpp +++ b/src/subscriptions/SubscriptionManager.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -247,7 +248,6 @@ SubscriptionManager::pubBookChanges( ripple::LedgerInfo const& lgrInfo, std::vector const& transactions) { - // TODO: change to ngRPC after old RPC is removed auto const json = RPC::computeBookChanges(lgrInfo, transactions); auto const bookChangesMsg = std::make_shared(boost::json::serialize(json)); bookChangesSubscribers_.publish(bookChangesMsg); diff --git a/src/webserver/Context.h b/src/webserver/Context.h new file mode 100644 index 00000000..c195b11c --- /dev/null +++ b/src/webserver/Context.h @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and 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. +*/ +//============================================================================== + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +class WsBase; + +namespace Web { + +struct Context : public util::Taggable +{ + clio::Logger perfLog_{"Performance"}; + boost::asio::yield_context& yield; + std::string method; + std::uint32_t version; + boost::json::object const& params; + std::shared_ptr session; + Backend::LedgerRange const& range; + std::string clientIp; + + Context( + boost::asio::yield_context& yield_, + std::string const& command_, + std::uint32_t version_, + boost::json::object const& params_, + std::shared_ptr const& session_, + util::TagDecoratorFactory const& tagFactory_, + Backend::LedgerRange const& range_, + std::string const& clientIp_) + : Taggable(tagFactory_) + , yield(yield_) + , method(command_) + , version(version_) + , params(params_) + , session(session_) + , range(range_) + , clientIp(clientIp_) + { + perfLog_.debug() << tag() << "new Context created"; + } +}; + +} // namespace Web diff --git a/src/webserver/HttpBase.h b/src/webserver/HttpBase.h index a7d6e804..db86a7b7 100644 --- a/src/webserver/HttpBase.h +++ b/src/webserver/HttpBase.h @@ -109,13 +109,12 @@ class HttpBase : public util::Taggable http::request req_; std::shared_ptr res_; std::shared_ptr backend_; + std::shared_ptr rpcEngine_; std::shared_ptr subscriptions_; std::shared_ptr balancer_; std::shared_ptr etl_; util::TagDecoratorFactory const& tagFactory_; clio::DOSGuard& dosGuard_; - RPC::Counters& counters_; - WorkQueue& workQueue_; send_lambda lambda_; protected: @@ -165,24 +164,22 @@ public: HttpBase( boost::asio::io_context& ioc, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer buffer) : Taggable(tagFactory) , ioc_(ioc) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , workQueue_(queue) , lambda_(*this) , buffer_(std::move(buffer)) { @@ -262,13 +259,12 @@ public: std::move(req_), std::move(buffer_), backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, - dosGuard_, - counters_, - workQueue_); + dosGuard_); } // to avoid overwhelm work queue, the request limit check should be @@ -283,26 +279,24 @@ public: auto session = derived().shared_from_this(); - // Requests are handed using coroutines. Here we spawn a coroutine - // which will asynchronously handle a request. - if (!workQueue_.postCoro( + if (not rpcEngine_->post( [this, ip, session](boost::asio::yield_context yield) { handle_request( yield, std::move(req_), lambda_, backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, dosGuard_, - counters_, *ip, session, perfLog_); }, - dosGuard_.isWhiteListed(*ip))) + ip.value())) { // Non-whitelist connection rejected due to full connection // queue @@ -347,12 +341,12 @@ handle_request( boost::beast::http::request>&& req, Send&& send, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, std::string const& ip, std::shared_ptr http, clio::Logger& perfLog) @@ -404,17 +398,7 @@ handle_request( "application/json", boost::json::serialize(RPC::makeError(RPC::RippledError::rpcNOT_READY)))); - std::optional context = RPC::make_HttpContext( - yc, - request, - backend, - subscriptions, - balancer, - etl, - tagFactory.with(std::cref(http->tag())), - *range, - counters, - ip); + auto context = RPC::make_HttpContext(yc, request, tagFactory.with(std::cref(http->tag())), *range, ip); if (!context) return send(httpResponse( @@ -423,14 +407,14 @@ handle_request( boost::json::serialize(RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX)))); boost::json::object response; - auto [v, timeDiff] = util::timed([&]() { return RPC::buildResponse(*context); }); + auto [v, timeDiff] = util::timed([&]() { return rpcEngine->buildResponse(*context); }); auto us = std::chrono::duration(timeDiff); RPC::logDuration(*context, us); if (auto status = std::get_if(&v)) { - counters.rpcErrored(context->method); + rpcEngine->notifyErrored(context->method); auto error = RPC::makeError(*status); error["request"] = request; response["result"] = error; @@ -442,7 +426,7 @@ handle_request( // This can still technically be an error. Clio counts forwarded // requests as successful. - counters.rpcComplete(context->method, us); + rpcEngine->notifyComplete(context->method, us); auto result = std::get(v); if (result.contains("result") && result.at("result").is_object()) @@ -456,7 +440,7 @@ handle_request( boost::json::array warnings; warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_CLIO)); - auto lastCloseAge = context->etl->lastCloseAgeSeconds(); + auto lastCloseAge = etl->lastCloseAgeSeconds(); if (lastCloseAge >= 60) warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED)); response["warnings"] = warnings; diff --git a/src/webserver/HttpSession.h b/src/webserver/HttpSession.h index 7e9eca26..8a42f25d 100644 --- a/src/webserver/HttpSession.h +++ b/src/webserver/HttpSession.h @@ -38,24 +38,22 @@ public: boost::asio::io_context& ioc, tcp::socket&& socket, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer buffer) : HttpBase( ioc, backend, + rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, - counters, - queue, std::move(buffer)) , stream_(std::move(socket)) { diff --git a/src/webserver/Listener.h b/src/webserver/Listener.h index 330382ab..9700888a 100644 --- a/src/webserver/Listener.h +++ b/src/webserver/Listener.h @@ -45,13 +45,12 @@ class Detector : public std::enable_shared_from_this> ctx_; std::shared_ptr backend_; + std::shared_ptr rpcEngine_; std::shared_ptr subscriptions_; std::shared_ptr balancer_; std::shared_ptr etl_; util::TagDecoratorFactory const& tagFactory_; clio::DOSGuard& dosGuard_; - RPC::Counters& counters_; - WorkQueue& queue_; boost::beast::flat_buffer buffer_; public: @@ -60,24 +59,22 @@ public: tcp::socket&& socket, std::optional> ctx, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, - clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue) + clio::DOSGuard& dosGuard) : ioc_(ioc) , stream_(std::move(socket)) , ctx_(ctx) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) { } @@ -116,13 +113,12 @@ public: stream_.release_socket(), *ctx_, backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, dosGuard_, - counters_, - queue_, std::move(buffer_)) ->run(); return; @@ -133,13 +129,12 @@ public: ioc_, stream_.release_socket(), backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, dosGuard_, - counters_, - queue_, std::move(buffer_)) ->run(); } @@ -153,26 +148,24 @@ make_websocket_session( http::request req, boost::beast::flat_buffer buffer, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, - clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue) + clio::DOSGuard& dosGuard) { std::make_shared( ioc, std::move(stream), ip, backend, + rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, - counters, - queue, std::move(buffer), std::move(req)) ->run(); @@ -186,26 +179,24 @@ make_websocket_session( http::request req, boost::beast::flat_buffer buffer, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, - clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue) + clio::DOSGuard& dosGuard) { std::make_shared( ioc, std::move(stream), ip, backend, + rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, - counters, - queue, std::move(buffer), std::move(req)) ->run(); @@ -221,22 +212,20 @@ class Listener : public std::enable_shared_from_this> ctx_; tcp::acceptor acceptor_; std::shared_ptr backend_; + std::shared_ptr rpcEngine_; std::shared_ptr subscriptions_; std::shared_ptr balancer_; std::shared_ptr etl_; util::TagDecoratorFactory tagFactory_; clio::DOSGuard& dosGuard_; - WorkQueue queue_; - RPC::Counters counters_; public: Listener( boost::asio::io_context& ioc, - uint32_t numWorkerThreads, - uint32_t maxQueueSize, std::optional> ctx, tcp::endpoint endpoint, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, @@ -246,13 +235,12 @@ public: , ctx_(ctx) , acceptor_(net::make_strand(ioc)) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(std::move(tagFactory)) , dosGuard_(dosGuard) - , queue_(numWorkerThreads, maxQueueSize) - , counters_(queue_) { boost::beast::error_code ec; @@ -311,13 +299,12 @@ private: std::move(socket), ctxRef, backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, - dosGuard_, - counters_, - queue_) + dosGuard_) ->run(); } @@ -337,6 +324,7 @@ make_HttpServer( boost::asio::io_context& ioc, std::optional> sslCtx, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, @@ -349,18 +337,13 @@ make_HttpServer( auto const serverConfig = config.section("server"); auto const address = boost::asio::ip::make_address(serverConfig.value("ip")); auto const port = serverConfig.value("port"); - auto const numThreads = config.valueOr("workers", std::thread::hardware_concurrency()); - auto const maxQueueSize = serverConfig.valueOr("max_queue_size", 0); // 0 is no limit - - log.info() << "Number of workers = " << numThreads << ". Max queue size = " << maxQueueSize; auto server = std::make_shared( ioc, - numThreads, - maxQueueSize, sslCtx, boost::asio::ip::tcp::endpoint{address, port}, backend, + rpcEngine, subscriptions, balancer, etl, diff --git a/src/webserver/PlainWsSession.h b/src/webserver/PlainWsSession.h index 4aa42243..a18c90b7 100644 --- a/src/webserver/PlainWsSession.h +++ b/src/webserver/PlainWsSession.h @@ -52,26 +52,14 @@ public: boost::asio::ip::tcp::socket&& socket, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& buffer) - : WsSession( - ioc, - ip, - backend, - subscriptions, - balancer, - etl, - tagFactory, - dosGuard, - counters, - queue, - std::move(buffer)) + : WsSession(ioc, ip, backend, rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, std::move(buffer)) , ws_(std::move(socket)) { } @@ -98,13 +86,12 @@ class WsUpgrader : public std::enable_shared_from_this boost::optional> parser_; boost::beast::flat_buffer buffer_; std::shared_ptr backend_; + std::shared_ptr rpcEngine_; std::shared_ptr subscriptions_; std::shared_ptr balancer_; std::shared_ptr etl_; util::TagDecoratorFactory const& tagFactory_; clio::DOSGuard& dosGuard_; - RPC::Counters& counters_; - WorkQueue& queue_; http::request req_; std::optional ip_; @@ -114,25 +101,23 @@ public: boost::asio::ip::tcp::socket&& socket, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& b) : ioc_(ioc) , http_(std::move(socket)) , buffer_(std::move(b)) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) , ip_(ip) { } @@ -141,26 +126,24 @@ public: boost::beast::tcp_stream&& stream, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& b, http::request req) : ioc_(ioc) , http_(std::move(stream)) , buffer_(std::move(b)) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) , req_(std::move(req)) , ip_(ip) { @@ -210,13 +193,12 @@ private: http_.release_socket(), ip_, backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, dosGuard_, - counters_, - queue_, std::move(buffer_)) ->run(std::move(req_)); } diff --git a/src/webserver/SslHttpSession.h b/src/webserver/SslHttpSession.h index 2b977e44..22908089 100644 --- a/src/webserver/SslHttpSession.h +++ b/src/webserver/SslHttpSession.h @@ -39,24 +39,22 @@ public: tcp::socket&& socket, ssl::context& ctx, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer buffer) : HttpBase( ioc, backend, + rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, - counters, - queue, std::move(buffer)) , stream_(std::move(socket), ctx) { diff --git a/src/webserver/SslWsSession.h b/src/webserver/SslWsSession.h index 728d7a28..8836f0b6 100644 --- a/src/webserver/SslWsSession.h +++ b/src/webserver/SslWsSession.h @@ -48,15 +48,14 @@ public: boost::beast::ssl_stream&& stream, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& b) - : WsSession(ioc, ip, backend, subscriptions, balancer, etl, tagFactory, dosGuard, counters, queue, std::move(b)) + : WsSession(ioc, ip, backend, rpcEngine, subscriptions, balancer, etl, tagFactory, dosGuard, std::move(b)) , ws_(std::move(stream)) { } @@ -81,13 +80,12 @@ class SslWsUpgrader : public std::enable_shared_from_this boost::beast::flat_buffer buffer_; std::optional ip_; std::shared_ptr backend_; + std::shared_ptr rpcEngine_; std::shared_ptr subscriptions_; std::shared_ptr balancer_; std::shared_ptr etl_; util::TagDecoratorFactory const& tagFactory_; clio::DOSGuard& dosGuard_; - RPC::Counters& counters_; - WorkQueue& queue_; http::request req_; public: @@ -97,26 +95,24 @@ public: boost::asio::ip::tcp::socket&& socket, ssl::context& ctx, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& b) : ioc_(ioc) , https_(std::move(socket), ctx) , buffer_(std::move(b)) , ip_(ip) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) { } SslWsUpgrader( @@ -124,13 +120,12 @@ public: boost::beast::ssl_stream stream, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& b, http::request req) : ioc_(ioc) @@ -138,13 +133,12 @@ public: , buffer_(std::move(b)) , ip_(ip) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) , req_(std::move(req)) { } @@ -207,13 +201,12 @@ private: std::move(https_), ip_, backend_, + rpcEngine_, subscriptions_, balancer_, etl_, tagFactory_, dosGuard_, - counters_, - queue_, std::move(buffer_)) ->run(std::move(req_)); } diff --git a/src/webserver/WsBase.h b/src/webserver/WsBase.h index 2b99c609..e34df918 100644 --- a/src/webserver/WsBase.h +++ b/src/webserver/WsBase.h @@ -103,7 +103,6 @@ public: class SubscriptionManager; class ETLLoadBalancer; -// Echoes back all received WebSocket messages template class WsSession : public WsBase, public std::enable_shared_from_this> { @@ -113,6 +112,7 @@ class WsSession : public WsBase, public std::enable_shared_from_this backend_; + std::shared_ptr rpcEngine_; // has to be a weak ptr because SubscriptionManager maintains collections // of std::shared_ptr objects. If this were shared, there would be // a cyclical dependency that would block destruction @@ -121,8 +121,6 @@ class WsSession : public WsBase, public std::enable_shared_from_this etl_; util::TagDecoratorFactory const& tagFactory_; clio::DOSGuard& dosGuard_; - RPC::Counters& counters_; - WorkQueue& queue_; std::mutex mtx_; bool sending_ = false; @@ -150,25 +148,23 @@ public: boost::asio::io_context& ioc, std::optional ip, std::shared_ptr backend, + std::shared_ptr rpcEngine, std::shared_ptr subscriptions, std::shared_ptr balancer, std::shared_ptr etl, util::TagDecoratorFactory const& tagFactory, clio::DOSGuard& dosGuard, - RPC::Counters& counters, - WorkQueue& queue, boost::beast::flat_buffer&& buffer) : WsBase(tagFactory) , buffer_(std::move(buffer)) , ioc_(ioc) , backend_(backend) + , rpcEngine_(rpcEngine) , subscriptions_(subscriptions) , balancer_(balancer) , etl_(etl) , tagFactory_(tagFactory) , dosGuard_(dosGuard) - , counters_(counters) - , queue_(queue) , ip_(ip) { perfLog_.info() << tag() << "session created"; @@ -181,8 +177,6 @@ public: dosGuard_.decrement(*ip_); } - // Access the derived class, this is part of - // the Curiously Recurring Template Pattern idiom. Derived& derived() { @@ -304,18 +298,8 @@ public: if (!range) return sendError(RPC::RippledError::rpcNOT_READY); - std::optional context = RPC::make_WsContext( - yield, - request, - backend_, - subscriptions_.lock(), - balancer_, - etl_, - shared_from_this(), - tagFactory_.with(std::cref(tag())), - *range, - counters_, - *ip); + auto context = RPC::make_WsContext( + yield, request, shared_from_this(), tagFactory_.with(std::cref(tag())), *range, *ip); if (!context) { @@ -325,15 +309,14 @@ public: response = getDefaultWsResponse(id); - auto [v, timeDiff] = util::timed([&]() { return RPC::buildResponse(*context); }); + auto [v, timeDiff] = util::timed([this, &context]() { return rpcEngine_->buildResponse(*context); }); auto us = std::chrono::duration(timeDiff); - logDuration(*context, us); + RPC::logDuration(*context, us); if (auto status = std::get_if(&v)) { - counters_.rpcErrored(context->method); - + rpcEngine_->notifyErrored(context->method); auto error = RPC::makeError(*status); if (!id.is_null()) @@ -344,7 +327,7 @@ public: } else { - counters_.rpcComplete(context->method, us); + rpcEngine_->notifyComplete(context->method, us); auto const& result = std::get(v); auto const isForwarded = result.contains("forwarded") && result.at("forwarded").is_bool() && @@ -445,11 +428,11 @@ public: auto id = request.contains("id") ? request.at("id") : nullptr; perfLog_.debug() << tag() << "Adding to work queue"; - if (!queue_.postCoro( - [shared_this = shared_from_this(), r = std::move(request), id](boost::asio::yield_context yield) { - shared_this->handle_request(std::move(r), id, yield); + if (not rpcEngine_->post( + [self = shared_from_this(), req = std::move(request), id](boost::asio::yield_context yield) { + self->handle_request(std::move(req), id, yield); }, - dosGuard_.isWhiteListed(*ip))) + ip.value())) sendError(RPC::RippledError::rpcTOO_BUSY, id, request); } diff --git a/unittests/Backend.cpp b/unittests/Backend.cpp index 585dbff0..637be4b0 100644 --- a/unittests/Backend.cpp +++ b/unittests/Backend.cpp @@ -33,6 +33,8 @@ #include +using namespace RPC; + class BackendTest : public NoLoggerFixture { }; @@ -94,7 +96,7 @@ TEST_F(BackendTest, Basic) return uint.fromVoid((void const*)bin.data()); }; auto ledgerInfoToBinaryString = [](auto const& info) { - auto blob = RPC::ledgerInfoToBlob(info, true); + auto blob = ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { @@ -126,7 +128,7 @@ TEST_F(BackendTest, Basic) auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); + EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value()); @@ -158,11 +160,11 @@ TEST_F(BackendTest, Basic) auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield); EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value()); @@ -473,7 +475,7 @@ TEST_F(BackendTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 1); EXPECT_STREQ((const char*)txns[0].transaction.data(), (const char*)txnBlob.data()); @@ -540,7 +542,7 @@ TEST_F(BackendTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); @@ -580,7 +582,7 @@ TEST_F(BackendTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); @@ -725,11 +727,11 @@ TEST_F(BackendTest, Basic) EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); // retLgr = backend->fetchLedgerByHash(lgrInfo.hash); // EXPECT_TRUE(retLgr); - // EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), - // RPC::ledgerInfoToBlob(lgrInfo)); + // EXPECT_EQ(ledgerInfoToBlob(*retLgr), + // ledgerInfoToBlob(lgrInfo)); auto retTxns = backend->fetchAllTransactionsInLedger(seq, yield); for (auto [hash, txn, meta] : txns) { @@ -1634,7 +1636,7 @@ TEST_F(BackendTest, cacheIntegration) return uint.fromVoid((void const*)bin.data()); }; auto ledgerInfoToBinaryString = [](auto const& info) { - auto blob = RPC::ledgerInfoToBlob(info, true); + auto blob = ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { @@ -1668,7 +1670,7 @@ TEST_F(BackendTest, cacheIntegration) auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); + EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value()); @@ -1700,12 +1702,12 @@ TEST_F(BackendTest, cacheIntegration) auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield); EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value()); @@ -1742,7 +1744,7 @@ TEST_F(BackendTest, cacheIntegration) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject(key256, lgrInfoNext.seq, yield); @@ -1909,10 +1911,10 @@ TEST_F(BackendTest, cacheIntegration) EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); retLgr = backend->fetchLedgerByHash(lgrInfo.hash, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)) + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)) << "retLgr seq:" << retLgr->seq << "; lgrInfo seq:" << lgrInfo.seq << "; retLgr hash:" << retLgr->hash << "; lgrInfo hash:" << lgrInfo.hash << "; retLgr parentHash:" << retLgr->parentHash << "; lgr Info parentHash:" << lgrInfo.parentHash; diff --git a/unittests/backend/cassandra/BackendTests.cpp b/unittests/backend/cassandra/BackendTests.cpp index fb465def..08ec1866 100644 --- a/unittests/backend/cassandra/BackendTests.cpp +++ b/unittests/backend/cassandra/BackendTests.cpp @@ -31,6 +31,7 @@ using namespace clio; using namespace std; +using namespace RPC; namespace json = boost::json; using namespace Backend::Cassandra; @@ -106,7 +107,7 @@ TEST_F(BackendCassandraTest, Basic) return uint.fromVoid((void const*)bin.data()); }; [[maybe_unused]] auto ledgerInfoToBinaryString = [](auto const& info) { - auto blob = RPC::ledgerInfoToBlob(info, true); + auto blob = ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { @@ -136,7 +137,7 @@ TEST_F(BackendCassandraTest, Basic) auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); + EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value()); @@ -167,11 +168,11 @@ TEST_F(BackendCassandraTest, Basic) auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield); EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value()); @@ -430,7 +431,7 @@ TEST_F(BackendCassandraTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); ASSERT_EQ(txns.size(), 1); EXPECT_STREQ((const char*)txns[0].transaction.data(), (const char*)txnBlob.data()); @@ -487,7 +488,7 @@ TEST_F(BackendCassandraTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); @@ -526,7 +527,7 @@ TEST_F(BackendCassandraTest, Basic) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); @@ -669,7 +670,7 @@ TEST_F(BackendCassandraTest, Basic) EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); auto retTxns = backend->fetchAllTransactionsInLedger(seq, yield); for (auto [hash, txn, meta] : txns) { @@ -937,7 +938,7 @@ TEST_F(BackendCassandraTest, CacheIntegration) return uint.fromVoid((void const*)bin.data()); }; auto ledgerInfoToBinaryString = [](auto const& info) { - auto blob = RPC::ledgerInfoToBlob(info, true); + auto blob = ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { @@ -970,7 +971,7 @@ TEST_F(BackendCassandraTest, CacheIntegration) auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); + EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value()); auto lgrInfoOld = lgrInfo; @@ -1001,12 +1002,12 @@ TEST_F(BackendCassandraTest, CacheIntegration) auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); - EXPECT_NE(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield); EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value()); @@ -1039,7 +1040,7 @@ TEST_F(BackendCassandraTest, CacheIntegration) EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject(key256, lgrInfoNext.seq, yield); @@ -1206,10 +1207,10 @@ TEST_F(BackendCassandraTest, CacheIntegration) EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); retLgr = backend->fetchLedgerByHash(lgrInfo.hash, yield); EXPECT_TRUE(retLgr); - EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)) + EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)) << "retLgr seq:" << retLgr->seq << "; lgrInfo seq:" << lgrInfo.seq << "; retLgr hash:" << retLgr->hash << "; lgrInfo hash:" << lgrInfo.hash << "; retLgr parentHash:" << retLgr->parentHash << "; lgr Info parentHash:" << lgrInfo.parentHash; diff --git a/unittests/rpc/BaseTests.cpp b/unittests/rpc/BaseTests.cpp index a48f1f13..f7865fb7 100644 --- a/unittests/rpc/BaseTests.cpp +++ b/unittests/rpc/BaseTests.cpp @@ -32,8 +32,8 @@ using namespace clio; using namespace std; -using namespace RPCng; -using namespace RPCng::validation; +using namespace RPC; +using namespace RPC::validation; namespace json = boost::json; diff --git a/unittests/rpc/handlers/AccountChannelsTest.cpp b/unittests/rpc/handlers/AccountChannelsTest.cpp index 2b0974e2..3eda6f4e 100644 --- a/unittests/rpc/handlers/AccountChannelsTest.cpp +++ b/unittests/rpc/handlers/AccountChannelsTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountCurrenciesTest.cpp b/unittests/rpc/handlers/AccountCurrenciesTest.cpp index 2cdf9bb3..56a04df9 100644 --- a/unittests/rpc/handlers/AccountCurrenciesTest.cpp +++ b/unittests/rpc/handlers/AccountCurrenciesTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountInfoTest.cpp b/unittests/rpc/handlers/AccountInfoTest.cpp index 220dc9dc..31e5a637 100644 --- a/unittests/rpc/handlers/AccountInfoTest.cpp +++ b/unittests/rpc/handlers/AccountInfoTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountLinesTest.cpp b/unittests/rpc/handlers/AccountLinesTest.cpp index 5c1067de..51a70139 100644 --- a/unittests/rpc/handlers/AccountLinesTest.cpp +++ b/unittests/rpc/handlers/AccountLinesTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountNFTsTest.cpp b/unittests/rpc/handlers/AccountNFTsTest.cpp index ed7f0539..6e5e9aeb 100644 --- a/unittests/rpc/handlers/AccountNFTsTest.cpp +++ b/unittests/rpc/handlers/AccountNFTsTest.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -36,7 +36,7 @@ constexpr static auto PAGE = "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255A constexpr static auto MAXSEQ = 30; constexpr static auto MINSEQ = 10; -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; @@ -156,7 +156,6 @@ TEST_P(AccountNFTParameterTest, InvalidParams) auto const output = handler.process(req, Context{std::ref(yield)}); ASSERT_FALSE(output); auto const err = RPC::makeError(output.error()); - std::cout << err << std::endl; EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); }); @@ -326,7 +325,6 @@ TEST_F(RPCAccountNFTsHandlerTest, NormalPath) runSpawn([&](auto& yield) { auto const output = handler.process(input, Context{std::ref(yield)}); ASSERT_TRUE(output); - std::cout << output.value() << std::endl; EXPECT_EQ(*output, json::parse(expectedOutput)); }); } diff --git a/unittests/rpc/handlers/AccountObjectsTest.cpp b/unittests/rpc/handlers/AccountObjectsTest.cpp index eeabc3c0..c9cef9da 100644 --- a/unittests/rpc/handlers/AccountObjectsTest.cpp +++ b/unittests/rpc/handlers/AccountObjectsTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountOffersTest.cpp b/unittests/rpc/handlers/AccountOffersTest.cpp index 25d2cb5d..ea769248 100644 --- a/unittests/rpc/handlers/AccountOffersTest.cpp +++ b/unittests/rpc/handlers/AccountOffersTest.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -29,7 +29,7 @@ constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"; -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/AccountTxTest.cpp b/unittests/rpc/handlers/AccountTxTest.cpp index 35d68b68..e49d92d0 100644 --- a/unittests/rpc/handlers/AccountTxTest.cpp +++ b/unittests/rpc/handlers/AccountTxTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/BookChangesTest.cpp b/unittests/rpc/handlers/BookChangesTest.cpp index dfd70b8f..5783e1b0 100644 --- a/unittests/rpc/handlers/BookChangesTest.cpp +++ b/unittests/rpc/handlers/BookChangesTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/BookOffersTest.cpp b/unittests/rpc/handlers/BookOffersTest.cpp index c5c27f20..8d2d0a63 100644 --- a/unittests/rpc/handlers/BookOffersTest.cpp +++ b/unittests/rpc/handlers/BookOffersTest.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -38,7 +38,7 @@ constexpr static auto PAYS20XRPGETS10USDBOOKDIR = "7B1767D41DBCE79D9585CF9D0262A // transfer rate x2 constexpr static auto TRANSFERRATEX2 = 2000000000; -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/DefaultProcessorTests.cpp b/unittests/rpc/handlers/DefaultProcessorTests.cpp index d08ff2cf..6bc76eae 100644 --- a/unittests/rpc/handlers/DefaultProcessorTests.cpp +++ b/unittests/rpc/handlers/DefaultProcessorTests.cpp @@ -27,8 +27,8 @@ using namespace testing; using namespace std; -using namespace RPCng; -using namespace RPCng::validation; +using namespace RPC; +using namespace RPC::validation; using namespace unittests::detail; namespace json = boost::json; @@ -40,7 +40,7 @@ class RPCDefaultProcessorTest : public NoLoggerFixture TEST_F(RPCDefaultProcessorTest, ValidInput) { HandlerMock handler; - RPCng::detail::DefaultProcessor processor; + RPC::detail::DefaultProcessor processor; auto const input = json::parse(R"({ "something": "works" })"); auto const spec = RpcSpec{{"something", Required{}}}; @@ -55,7 +55,7 @@ TEST_F(RPCDefaultProcessorTest, ValidInput) TEST_F(RPCDefaultProcessorTest, NoInputVaildCall) { HandlerWithoutInputMock handler; - RPCng::detail::DefaultProcessor processor; + RPC::detail::DefaultProcessor processor; auto const data = InOutFake{"works"}; auto const input = json::parse(R"({})"); @@ -68,7 +68,7 @@ TEST_F(RPCDefaultProcessorTest, NoInputVaildCall) TEST_F(RPCDefaultProcessorTest, InvalidInput) { HandlerMock handler; - RPCng::detail::DefaultProcessor processor; + RPC::detail::DefaultProcessor processor; auto const input = json::parse(R"({ "other": "nope" })"); auto const spec = RpcSpec{{"something", Required{}}}; diff --git a/unittests/rpc/handlers/GatewayBalancesTest.cpp b/unittests/rpc/handlers/GatewayBalancesTest.cpp index 2130ea71..601306a9 100644 --- a/unittests/rpc/handlers/GatewayBalancesTest.cpp +++ b/unittests/rpc/handlers/GatewayBalancesTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/LedgerDataTest.cpp b/unittests/rpc/handlers/LedgerDataTest.cpp index a5932beb..c6dcad9d 100644 --- a/unittests/rpc/handlers/LedgerDataTest.cpp +++ b/unittests/rpc/handlers/LedgerDataTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/LedgerEntryTest.cpp b/unittests/rpc/handlers/LedgerEntryTest.cpp index 1fd80c0b..1082f2fb 100644 --- a/unittests/rpc/handlers/LedgerEntryTest.cpp +++ b/unittests/rpc/handlers/LedgerEntryTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/LedgerRangeTest.cpp b/unittests/rpc/handlers/LedgerRangeTest.cpp index 2a44dbe6..dfb27830 100644 --- a/unittests/rpc/handlers/LedgerRangeTest.cpp +++ b/unittests/rpc/handlers/LedgerRangeTest.cpp @@ -18,12 +18,12 @@ //============================================================================== #include -#include +#include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/LedgerTest.cpp b/unittests/rpc/handlers/LedgerTest.cpp index e1d7bbdf..5b2a3f47 100644 --- a/unittests/rpc/handlers/LedgerTest.cpp +++ b/unittests/rpc/handlers/LedgerTest.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -33,7 +33,7 @@ constexpr static auto INDEX2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B2501 constexpr static auto RANGEMIN = 10; constexpr static auto RANGEMAX = 30; -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; @@ -170,7 +170,6 @@ TEST_P(LedgerParameterTest, InvalidParams) auto const output = handler.process(req, Context{std::ref(yield)}); ASSERT_FALSE(output); auto const err = RPC::makeError(output.error()); - std::cout << err << std::endl; EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); }); diff --git a/unittests/rpc/handlers/NFTBuyOffersTest.cpp b/unittests/rpc/handlers/NFTBuyOffersTest.cpp index c5d43fee..8c0ac7c0 100644 --- a/unittests/rpc/handlers/NFTBuyOffersTest.cpp +++ b/unittests/rpc/handlers/NFTBuyOffersTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/NFTHistoryTest.cpp b/unittests/rpc/handlers/NFTHistoryTest.cpp index 6f358088..84f17d78 100644 --- a/unittests/rpc/handlers/NFTHistoryTest.cpp +++ b/unittests/rpc/handlers/NFTHistoryTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/NFTInfoTest.cpp b/unittests/rpc/handlers/NFTInfoTest.cpp index cda00078..d4c0434c 100644 --- a/unittests/rpc/handlers/NFTInfoTest.cpp +++ b/unittests/rpc/handlers/NFTInfoTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/NFTSellOffersTest.cpp b/unittests/rpc/handlers/NFTSellOffersTest.cpp index 4447a3f6..7646b707 100644 --- a/unittests/rpc/handlers/NFTSellOffersTest.cpp +++ b/unittests/rpc/handlers/NFTSellOffersTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/NoRippleCheckTest.cpp b/unittests/rpc/handlers/NoRippleCheckTest.cpp index c97393e7..90574628 100644 --- a/unittests/rpc/handlers/NoRippleCheckTest.cpp +++ b/unittests/rpc/handlers/NoRippleCheckTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/PingTest.cpp b/unittests/rpc/handlers/PingTest.cpp index 42c56dca..a2f57618 100644 --- a/unittests/rpc/handlers/PingTest.cpp +++ b/unittests/rpc/handlers/PingTest.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include +#include #include -using namespace RPCng; +using namespace RPC; class RPCPingHandlerTest : public NoLoggerFixture { diff --git a/unittests/rpc/handlers/RandomTest.cpp b/unittests/rpc/handlers/RandomTest.cpp index f4d1a68f..7585261d 100644 --- a/unittests/rpc/handlers/RandomTest.cpp +++ b/unittests/rpc/handlers/RandomTest.cpp @@ -19,10 +19,10 @@ #include #include -#include +#include #include -using namespace RPCng; +using namespace RPC; class RPCRandomHandlerTest : public NoLoggerFixture { diff --git a/unittests/rpc/handlers/ServerInfoTest.cpp b/unittests/rpc/handlers/ServerInfoTest.cpp index 61f27948..a59a7ad4 100644 --- a/unittests/rpc/handlers/ServerInfoTest.cpp +++ b/unittests/rpc/handlers/ServerInfoTest.cpp @@ -18,11 +18,11 @@ //============================================================================== #include -#include +#include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; @@ -60,7 +60,7 @@ protected: } void - validateNormalOutput(RPCng::ReturnType const& output) + validateNormalOutput(RPC::ReturnType const& output) { ASSERT_TRUE(output); auto const& result = output.value().as_object(); @@ -96,7 +96,7 @@ protected: } void - validateAdminOutput(RPCng::ReturnType const& output) + validateAdminOutput(RPC::ReturnType const& output) { auto const& result = output.value().as_object(); auto const& info = result.at("info").as_object(); @@ -105,7 +105,7 @@ protected: } void - validateRippledOutput(RPCng::ReturnType const& output) + validateRippledOutput(RPC::ReturnType const& output) { auto const& result = output.value().as_object(); auto const& info = result.at("info").as_object(); @@ -118,7 +118,7 @@ protected: } void - validateCacheOutput(RPCng::ReturnType const& output) + validateCacheOutput(RPC::ReturnType const& output) { auto const& result = output.value().as_object(); auto const& info = result.at("info").as_object(); diff --git a/unittests/rpc/handlers/SubscribeTest.cpp b/unittests/rpc/handlers/SubscribeTest.cpp index 7107e70e..710fefbc 100644 --- a/unittests/rpc/handlers/SubscribeTest.cpp +++ b/unittests/rpc/handlers/SubscribeTest.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include #include @@ -29,7 +29,7 @@ #include using namespace std::chrono_literals; -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/TestHandlerTests.cpp b/unittests/rpc/handlers/TestHandlerTests.cpp index 8339337e..70e2d2f0 100644 --- a/unittests/rpc/handlers/TestHandlerTests.cpp +++ b/unittests/rpc/handlers/TestHandlerTests.cpp @@ -25,8 +25,8 @@ #include using namespace std; -using namespace RPCng; -using namespace RPCng::validation; +using namespace RPC; +using namespace RPC::validation; using namespace unittests::detail; namespace json = boost::json; diff --git a/unittests/rpc/handlers/TransactionEntryTest.cpp b/unittests/rpc/handlers/TransactionEntryTest.cpp index 8076b411..b618c254 100644 --- a/unittests/rpc/handlers/TransactionEntryTest.cpp +++ b/unittests/rpc/handlers/TransactionEntryTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/TxTest.cpp b/unittests/rpc/handlers/TxTest.cpp index 112b4dc4..06cb5477 100644 --- a/unittests/rpc/handlers/TxTest.cpp +++ b/unittests/rpc/handlers/TxTest.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include +#include #include #include #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/UnsubscribeTest.cpp b/unittests/rpc/handlers/UnsubscribeTest.cpp index 9391d0e2..5df5bcef 100644 --- a/unittests/rpc/handlers/UnsubscribeTest.cpp +++ b/unittests/rpc/handlers/UnsubscribeTest.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include #include @@ -26,7 +26,7 @@ #include -using namespace RPCng; +using namespace RPC; namespace json = boost::json; using namespace testing; diff --git a/unittests/rpc/handlers/impl/FakesAndMocks.h b/unittests/rpc/handlers/impl/FakesAndMocks.h index b2f0d2c4..2bc5c832 100644 --- a/unittests/rpc/handlers/impl/FakesAndMocks.h +++ b/unittests/rpc/handlers/impl/FakesAndMocks.h @@ -70,14 +70,14 @@ class HandlerFake public: using Input = TestInput; using Output = TestOutput; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; - RPCng::RpcSpecConstRef + RPC::RpcSpecConstRef spec() const { - using namespace RPCng::validation; + using namespace RPC::validation; - static const auto rpcSpec = RPCng::RpcSpec{ + static const auto rpcSpec = RPC::RpcSpec{ {"hello", Required{}, Type{}, EqualTo{"world"}}, {"limit", Type{}, Between{0, 100}}, // optional field }; @@ -98,14 +98,14 @@ class CoroutineHandlerFake public: using Input = TestInput; using Output = TestOutput; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; - RPCng::RpcSpecConstRef + RPC::RpcSpecConstRef spec() const { - using namespace RPCng::validation; + using namespace RPC::validation; - static const auto rpcSpec = RPCng::RpcSpec{ + static const auto rpcSpec = RPC::RpcSpec{ {"hello", Required{}, Type{}, EqualTo{"world"}}, {"limit", Type{}, Between{0, 100}}, // optional field }; @@ -114,7 +114,7 @@ public: } Result - process(Input input, RPCng::Context const& ctx) const + process(Input input, RPC::Context const& ctx) const { return Output{input.hello + '_' + std::to_string(input.limit.value_or(0))}; } @@ -124,7 +124,7 @@ class NoInputHandlerFake { public: using Output = TestOutput; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; Result process() const @@ -139,14 +139,14 @@ class FailingHandlerFake public: using Input = TestInput; using Output = TestOutput; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; - RPCng::RpcSpecConstRef + RPC::RpcSpecConstRef spec() const { - using namespace RPCng::validation; + using namespace RPC::validation; - static const auto rpcSpec = RPCng::RpcSpec{ + static const auto rpcSpec = RPC::RpcSpec{ {"hello", Required{}, Type{}, EqualTo{"world"}}, {"limit", Type{}, Between{0u, 100u}}, // optional field }; @@ -158,7 +158,7 @@ public: process([[maybe_unused]] Input input) const { // always fail - return RPCng::Error{RPC::Status{"Very custom error"}}; + return RPC::Error{RPC::Status{"Very custom error"}}; } }; @@ -189,16 +189,16 @@ struct HandlerMock { using Input = InOutFake; using Output = InOutFake; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; - MOCK_METHOD(RPCng::RpcSpecConstRef, spec, (), (const)); + MOCK_METHOD(RPC::RpcSpecConstRef, spec, (), (const)); MOCK_METHOD(Result, process, (Input), (const)); }; struct HandlerWithoutInputMock { using Output = InOutFake; - using Result = RPCng::HandlerReturnType; + using Result = RPC::HandlerReturnType; MOCK_METHOD(Result, process, (), (const)); };