Integrate nextgen RPC into clio (#572)

Fixes #592
This commit is contained in:
Alex Kremer
2023-05-04 16:15:36 +01:00
committed by GitHub
parent f1b3a6b511
commit d7d5d61747
145 changed files with 3208 additions and 6756 deletions

View File

@@ -40,7 +40,7 @@ function(add_converage module)
COMMAND COMMAND
${LLVM_COV_PATH}/llvm-cov report $<TARGET_FILE:${module}> ${LLVM_COV_PATH}/llvm-cov report $<TARGET_FILE:${module}>
-instr-profile=${module}.profdata -instr-profile=${module}.profdata
-ignore-filename-regex=".*_makefiles|.*unittests" -ignore-filename-regex=".*_makefiles|.*unittests|.*_deps"
-show-region-summary=false -show-region-summary=false
DEPENDS ${module}-ccov-preprocessing) DEPENDS ${module}-ccov-preprocessing)
@@ -51,7 +51,7 @@ function(add_converage module)
${LLVM_COV_PATH}/llvm-cov show $<TARGET_FILE:${module}> ${LLVM_COV_PATH}/llvm-cov show $<TARGET_FILE:${module}>
-instr-profile=${module}.profdata -show-line-counts-or-regions -instr-profile=${module}.profdata -show-line-counts-or-regions
-output-dir=${module}-llvm-cov -format="html" -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) DEPENDS ${module}-ccov-preprocessing)
add_custom_command( add_custom_command(

View File

@@ -65,69 +65,36 @@ target_sources(clio PRIVATE
src/rpc/RPCHelpers.cpp src/rpc/RPCHelpers.cpp
src/rpc/Counters.cpp src/rpc/Counters.cpp
src/rpc/WorkQueue.cpp src/rpc/WorkQueue.cpp
## NextGen RPC
src/rpc/common/Specs.cpp src/rpc/common/Specs.cpp
src/rpc/common/Validators.cpp src/rpc/common/Validators.cpp
## NextGen RPC handler # RPC impl
src/rpc/ngHandlers/AccountChannels.cpp src/rpc/common/impl/HandlerProvider.cpp
src/rpc/ngHandlers/AccountCurrencies.cpp ## RPC handler
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
src/rpc/handlers/AccountChannels.cpp src/rpc/handlers/AccountChannels.cpp
src/rpc/handlers/AccountCurrencies.cpp src/rpc/handlers/AccountCurrencies.cpp
src/rpc/handlers/AccountInfo.cpp src/rpc/handlers/AccountInfo.cpp
src/rpc/handlers/AccountLines.cpp src/rpc/handlers/AccountLines.cpp
src/rpc/handlers/AccountOffers.cpp src/rpc/handlers/AccountNFTs.cpp
src/rpc/handlers/AccountObjects.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/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/Ledger.cpp
src/rpc/handlers/LedgerData.cpp src/rpc/handlers/LedgerData.cpp
src/rpc/handlers/LedgerEntry.cpp src/rpc/handlers/LedgerEntry.cpp
src/rpc/handlers/LedgerRange.cpp src/rpc/handlers/LedgerRange.cpp
# Transaction src/rpc/handlers/NFTBuyOffers.cpp
src/rpc/handlers/Tx.cpp src/rpc/handlers/NFTHistory.cpp
src/rpc/handlers/TransactionEntry.cpp src/rpc/handlers/NFTInfo.cpp
src/rpc/handlers/AccountTx.cpp src/rpc/handlers/NFTOffersCommon.cpp
# Dex src/rpc/handlers/NFTSellOffers.cpp
src/rpc/handlers/BookChanges.cpp src/rpc/handlers/NoRippleCheck.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/Random.cpp src/rpc/handlers/Random.cpp
src/rpc/handlers/TransactionEntry.cpp
src/rpc/handlers/Tx.cpp
## Util
src/config/Config.cpp src/config/Config.cpp
src/log/Logger.cpp src/log/Logger.cpp
src/util/Taggable.cpp) src/util/Taggable.cpp)

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -29,6 +29,8 @@
#include <config/Config.h> #include <config/Config.h>
#include <etl/ReportingETL.h> #include <etl/ReportingETL.h>
#include <log/Logger.h> #include <log/Logger.h>
#include <rpc/Counters.h>
#include <rpc/common/impl/HandlerProvider.h>
#include <webserver/Listener.h> #include <webserver/Listener.h>
#include <boost/asio/dispatch.hpp> #include <boost/asio/dispatch.hpp>
@@ -129,11 +131,8 @@ parseCerts(Config const& config)
std::string key = contents.str(); std::string key = contents.str();
ssl::context ctx{ssl::context::tlsv12}; ssl::context ctx{ssl::context::tlsv12};
ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2); 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_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); ctx.use_private_key(boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem);
return ctx; return ctx;
@@ -211,8 +210,16 @@ try
// mode, ETL only publishes // mode, ETL only publishes
auto etl = ReportingETL::make_ReportingETL(config, ioc, backend, subscriptions, balancer, ledgers); 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<RPC::detail::ProductionHandlerProvider const>(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 // 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. // Blocks until stopped.
// When stopped, shared_ptrs fall out of scope // When stopped, shared_ptrs fall out of scope

View File

@@ -23,7 +23,7 @@
#include <set> #include <set>
namespace RPCng { namespace RPC {
/** /**
* @brief Represents an entry in the book_changes' changes array. * @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<Backend::TransactionAndMetadata> const& transactions);
} // namespace RPC

View File

@@ -40,13 +40,11 @@ Counters::initializeCounter(std::string const& method)
void void
Counters::rpcErrored(std::string const& method) Counters::rpcErrored(std::string const& method)
{ {
if (!validHandler(method))
return;
initializeCounter(method); initializeCounter(method);
std::shared_lock lk(mutex_); std::shared_lock lk(mutex_);
MethodInfo& counters = methodInfo_[method]; MethodInfo& counters = methodInfo_[method];
counters.started++; counters.started++;
counters.errored++; counters.errored++;
} }
@@ -54,13 +52,11 @@ Counters::rpcErrored(std::string const& method)
void void
Counters::rpcComplete(std::string const& method, std::chrono::microseconds const& rpcDuration) Counters::rpcComplete(std::string const& method, std::chrono::microseconds const& rpcDuration)
{ {
if (!validHandler(method))
return;
initializeCounter(method); initializeCounter(method);
std::shared_lock lk(mutex_); std::shared_lock lk(mutex_);
MethodInfo& counters = methodInfo_[method]; MethodInfo& counters = methodInfo_[method];
counters.started++; counters.started++;
counters.finished++; counters.finished++;
counters.duration += rpcDuration.count(); counters.duration += rpcDuration.count();
@@ -69,13 +65,11 @@ Counters::rpcComplete(std::string const& method, std::chrono::microseconds const
void void
Counters::rpcForwarded(std::string const& method) Counters::rpcForwarded(std::string const& method)
{ {
if (!validHandler(method))
return;
initializeCounter(method); initializeCounter(method);
std::shared_lock lk(mutex_); std::shared_lock lk(mutex_);
MethodInfo& counters = methodInfo_[method]; MethodInfo& counters = methodInfo_[method];
counters.forwarded++; counters.forwarded++;
} }
@@ -83,13 +77,14 @@ boost::json::object
Counters::report() const Counters::report() const
{ {
std::shared_lock lk(mutex_); std::shared_lock lk(mutex_);
boost::json::object obj = {}; auto obj = boost::json::object{};
obj[JS(rpc)] = boost::json::object{}; obj[JS(rpc)] = boost::json::object{};
auto& rpc = obj[JS(rpc)].as_object(); auto& rpc = obj[JS(rpc)].as_object();
for (auto const& [method, info] : methodInfo_) 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(started)] = std::to_string(info.started);
counters[JS(finished)] = std::to_string(info.finished); counters[JS(finished)] = std::to_string(info.finished);
counters[JS(errored)] = std::to_string(info.errored); counters[JS(errored)] = std::to_string(info.errored);
@@ -98,6 +93,7 @@ Counters::report() const
rpc[method] = std::move(counters); rpc[method] = std::move(counters);
} }
obj["work_queue"] = workQueue_.get().report(); obj["work_queue"] = workQueue_.get().report();
return obj; return obj;

View File

@@ -55,6 +55,12 @@ private:
public: public:
Counters(WorkQueue const& wq) : workQueue_(std::cref(wq)){}; Counters(WorkQueue const& wq) : workQueue_(std::cref(wq)){};
static Counters
make_Counters(WorkQueue const& wq)
{
return Counters{wq};
}
void void
rpcErrored(std::string const& method); rpcErrored(std::string const& method);

View File

@@ -47,7 +47,8 @@ getWarningInfo(WarningCode code)
"want to talk to rippled, include 'ledger_index':'current' in your " "want to talk to rippled, include 'ledger_index':'current' in your "
"request"}, "request"},
{warnRPC_OUTDATED, "This server may be out of date"}, {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; }; auto matchByCode = [code](auto const& info) { return info.code == code; };
if (auto it = find_if(begin(infos), end(infos), matchByCode); it != end(infos)) if (auto it = find_if(begin(infos), end(infos), matchByCode); it != end(infos))
@@ -59,10 +60,12 @@ getWarningInfo(WarningCode code)
boost::json::object boost::json::object
makeWarning(WarningCode code) makeWarning(WarningCode code)
{ {
boost::json::object json; auto json = boost::json::object{};
auto const& info = getWarningInfo(code); auto const& info = getWarningInfo(code);
json["id"] = code; json["id"] = code;
json["message"] = static_cast<string>(info.message); json["message"] = static_cast<string>(info.message);
return json; return json;
} }
@@ -94,6 +97,7 @@ makeError(RippledError err, optional<string_view> customError, optional<string_v
json["error_message"] = customMessage.value_or(info.message.c_str()).data(); json["error_message"] = customMessage.value_or(info.message.c_str()).data();
json["status"] = "error"; json["status"] = "error";
json["type"] = "response"; json["type"] = "response";
return json; return json;
} }
@@ -108,6 +112,7 @@ makeError(ClioError err, optional<string_view> customError, optional<string_view
json["error_message"] = customMessage.value_or(info.message).data(); json["error_message"] = customMessage.value_or(info.message).data();
json["status"] = "error"; json["status"] = "error";
json["type"] = "response"; json["type"] = "response";
return json; return json;
} }
@@ -120,9 +125,7 @@ makeError(Status const& status)
overloadSet{ overloadSet{
[&status, &wrapOptional](RippledError err) { [&status, &wrapOptional](RippledError err) {
if (err == ripple::rpcUNKNOWN) if (err == ripple::rpcUNKNOWN)
{
return boost::json::object{{"error", status.message}, {"type", "response"}, {"status", "error"}}; return boost::json::object{{"error", status.message}, {"type", "response"}, {"status", "error"}};
}
return makeError(err, wrapOptional(status.error), wrapOptional(status.message)); return makeError(err, wrapOptional(status.error), wrapOptional(status.message));
}, },
@@ -131,13 +134,11 @@ makeError(Status const& status)
}, },
}, },
status.code); status.code);
if (status.extraInfo) if (status.extraInfo)
{
for (auto& [key, value] : status.extraInfo.value()) for (auto& [key, value] : status.extraInfo.value())
{
res[key] = value; res[key] = value;
}
}
return res; return res;
} }

View File

@@ -78,8 +78,7 @@ struct Status
Status(CombinedError code, boost::json::object&& extraInfo) : code(code), extraInfo(std::move(extraInfo)){}; Status(CombinedError code, boost::json::object&& extraInfo) : code(code), extraInfo(std::move(extraInfo)){};
// HACK. Some rippled handlers explicitly specify errors. // HACK. Some rippled handlers explicitly specify errors.
// This means that we have to be able to duplicate this // This means that we have to be able to duplicate this functionality.
// functionality.
explicit Status(std::string const& message) : code(ripple::rpcUNKNOWN), message(message) explicit Status(std::string const& message) : code(ripple::rpcUNKNOWN), message(message)
{ {
} }
@@ -99,6 +98,7 @@ struct Status
{ {
if (auto err = std::get_if<RippledError>(&code)) if (auto err = std::get_if<RippledError>(&code))
return *err != RippledError::rpcSUCCESS; return *err != RippledError::rpcSUCCESS;
return true; return true;
} }
@@ -113,6 +113,7 @@ struct Status
{ {
if (auto err = std::get_if<RippledError>(&code)) if (auto err = std::get_if<RippledError>(&code))
return *err == other; return *err == other;
return false; return false;
} }
@@ -127,6 +128,7 @@ struct Status
{ {
if (auto err = std::get_if<ClioError>(&code)) if (auto err = std::get_if<ClioError>(&code))
return *err == other; return *err == other;
return false; return false;
} }
}; };
@@ -180,6 +182,7 @@ public:
explicit AccountNotFoundError(std::string const& acct) : account(acct) explicit AccountNotFoundError(std::string const& acct) : account(acct)
{ {
} }
const char* const char*
what() const throw() override what() const throw() override
{ {

View File

@@ -17,33 +17,43 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #pragma once
#include <rpc/ngHandlers/LedgerRange.h>
#include <rpc/common/AnyHandler.h>
#include <rpc/common/Types.h>
#include <memory>
#include <optional> #include <optional>
#include <string>
namespace RPCng { namespace RPC {
LedgerRangeHandler::Result class HandlerTable
LedgerRangeHandler::process() const
{ {
if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange) std::shared_ptr<HandlerProvider const> provider_;
{
return Output{*maybeRange};
}
else
{
return Error{RPC::Status{RPC::RippledError::rpcNOT_READY, "rangeNotFound"}};
}
}
void public:
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerRangeHandler::Output const& output) HandlerTable(std::shared_ptr<HandlerProvider const> const& provider) : provider_{provider}
{ {
jv = boost::json::object{ }
{JS(ledger_index_min), output.range.minSequence},
{JS(ledger_index_max), output.range.maxSequence},
};
}
} // namespace RPCng bool
contains(std::string const& method) const
{
return provider_->contains(method);
}
std::optional<AnyHandler>
getHandler(std::string const& command) const
{
return provider_->getHandler(command);
}
bool
isClioOnly(std::string const& command) const
{
return provider_->isClioOnly(command);
}
};
} // namespace RPC

View File

@@ -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 <rpc/RPC.h>
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

View File

@@ -17,29 +17,13 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #pragma once
#include <rpc/ngHandlers/Random.h>
#include <ripple/beast/utility/rngfill.h> #include <ripple/protocol/jss.h>
#include <ripple/crypto/csprng.h>
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 // Access (SF)ield name (S)trings
RandomHandler::process() const #define SFS(x) ripple::x.jsonName.c_str()
{
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

View File

@@ -19,8 +19,8 @@
#include <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <log/Logger.h> #include <log/Logger.h>
#include <rpc/Handlers.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/common/impl/HandlerProvider.h>
#include <webserver/HttpBase.h> #include <webserver/HttpBase.h>
#include <webserver/WsBase.h> #include <webserver/WsBase.h>
@@ -30,6 +30,7 @@
using namespace std; using namespace std;
using namespace clio; using namespace clio;
using namespace RPC;
// local to compilation unit loggers // local to compilation unit loggers
namespace { namespace {
@@ -38,49 +39,14 @@ clio::Logger gLog{"RPC"};
} // namespace } // namespace
namespace RPC { namespace RPC {
Context::Context(
boost::asio::yield_context& yield_,
string const& command_,
uint32_t version_,
boost::json::object const& params_,
shared_ptr<BackendInterface const> const& backend_,
shared_ptr<SubscriptionManager> const& subscriptions_,
shared_ptr<ETLLoadBalancer> const& balancer_,
shared_ptr<ReportingETL const> const& etl_,
shared_ptr<WsBase> 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<Context> optional<Web::Context>
make_WsContext( make_WsContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
shared_ptr<BackendInterface const> const& backend,
shared_ptr<SubscriptionManager> const& subscriptions,
shared_ptr<ETLLoadBalancer> const& balancer,
shared_ptr<ReportingETL const> const& etl,
shared_ptr<WsBase> const& session, shared_ptr<WsBase> const& session,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
Counters& counters,
string const& clientIp) string const& clientIp)
{ {
boost::json::value commandValue = nullptr; boost::json::value commandValue = nullptr;
@@ -93,22 +59,15 @@ make_WsContext(
return {}; return {};
string command = commandValue.as_string().c_str(); string command = commandValue.as_string().c_str();
return make_optional<Web::Context>(yc, command, 1, request, session, tagFactory, range, clientIp);
return make_optional<Context>(
yc, command, 1, request, backend, subscriptions, balancer, etl, session, tagFactory, range, counters, clientIp);
} }
optional<Context> optional<Web::Context>
make_HttpContext( make_HttpContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
shared_ptr<BackendInterface const> const& backend,
shared_ptr<SubscriptionManager> const& subscriptions,
shared_ptr<ETLLoadBalancer> const& balancer,
shared_ptr<ReportingETL const> const& etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
RPC::Counters& counters,
string const& clientIp) string const& clientIp)
{ {
if (!request.contains("method") || !request.at("method").is_string()) if (!request.contains("method") || !request.at("method").is_string())
@@ -130,105 +89,9 @@ make_HttpContext(
if (!array.at(0).is_object()) if (!array.at(0).is_object())
return {}; return {};
return make_optional<Context>( return make_optional<Web::Context>(yc, command, 1, array.at(0).as_object(), nullptr, tagFactory, range, clientIp);
yc,
command,
1,
array.at(0).as_object(),
backend,
subscriptions,
balancer,
etl,
nullptr,
tagFactory,
range,
counters,
clientIp);
} }
using LimitRange = tuple<uint32_t, uint32_t, uint32_t>;
using HandlerFunction = function<Result(Context const&)>;
struct Handler
{
string method;
function<Result(Context const&)> handler;
optional<LimitRange> limit;
bool isClioOnly = false;
};
class HandlerTable
{
unordered_map<string, Handler> handlerMap_;
public:
HandlerTable(initializer_list<Handler> handlers)
{
for (auto const& handler : handlers)
{
handlerMap_[handler.method] = std::move(handler);
}
}
bool
contains(string const& method)
{
return handlerMap_.contains(method);
}
optional<LimitRange>
getLimitRange(string const& command)
{
if (!handlerMap_.contains(command))
return {};
return handlerMap_[command].limit;
}
optional<HandlerFunction>
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<string> forwardCommands{ static unordered_set<string> forwardCommands{
"submit", "submit",
"submit_multisigned", "submit_multisigned",
@@ -240,57 +103,55 @@ static unordered_set<string> forwardCommands{
"channel_authorize", "channel_authorize",
"channel_verify"}; "channel_verify"};
bool RPCEngine::RPCEngine(
validHandler(string const& method) std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL> const& etl,
clio::DOSGuard const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> 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>
RPCEngine::make_RPCEngine(
clio::Config const& config,
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL> const& etl,
clio::DOSGuard const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> const& handlerProvider)
{
return std::make_shared<RPCEngine>(
backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider);
} }
bool bool
isClioOnly(string const& method) RPCEngine::validHandler(string const& method) const
{ {
return handlerTable.isClioOnly(method); return handlerTable_.contains(method) || forwardCommands.contains(method);
} }
bool bool
shouldSuppressValidatedFlag(RPC::Context const& context) RPCEngine::isClioOnly(string const& method) const
{ {
return boost::iequals(context.method, "subscribe") || boost::iequals(context.method, "unsubscribe"); return handlerTable_.isClioOnly(method);
}
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<uint32_t>(input), lo, hi);
}
else
{
limit = def;
}
return {};
} }
bool bool
shouldForwardToRippled(Context const& ctx) RPCEngine::shouldForwardToRippled(Web::Context const& ctx) const
{ {
auto request = ctx.params; auto request = ctx.params;
@@ -310,16 +171,15 @@ shouldForwardToRippled(Context const& ctx)
} }
Result Result
buildResponse(Context const& ctx) RPCEngine::buildResponse(Web::Context const& ctx)
{ {
if (shouldForwardToRippled(ctx)) if (shouldForwardToRippled(ctx))
{ {
boost::json::object toForward = ctx.params; auto toForward = ctx.params;
toForward["command"] = ctx.method; toForward["command"] = ctx.method;
auto res = ctx.balancer->forwardToRippled(toForward, ctx.clientIp, ctx.yield); auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
notifyForwarded(ctx.method);
ctx.counters.rpcForwarded(ctx.method);
if (!res) if (!res)
return Status{RippledError::rpcFAILED_TO_FORWARD}; return Status{RippledError::rpcFAILED_TO_FORWARD};
@@ -327,32 +187,30 @@ buildResponse(Context const& ctx)
return *res; return *res;
} }
if (ctx.method == "ping") if (backend_->isTooBusy())
return boost::json::object{};
if (ctx.backend->isTooBusy())
{ {
gLog.error() << "Database is too busy. Rejecting request"; gLog.error() << "Database is too busy. Rejecting request";
return Status{RippledError::rpcTOO_BUSY}; return Status{RippledError::rpcTOO_BUSY};
} }
auto method = handlerTable.getHandler(ctx.method); auto const method = handlerTable_.getHandler(ctx.method);
if (!method) if (!method)
return Status{RippledError::rpcUNKNOWN_COMMAND}; return Status{RippledError::rpcUNKNOWN_COMMAND};
try try
{ {
gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method << '`'; 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 << '`'; gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
if (auto object = get_if<boost::json::object>(&v); object && not shouldSuppressValidatedFlag(ctx)) if (v)
{ return v->as_object();
(*object)[JS(validated)] = true; else
} return Status{v.error()};
return v;
} }
catch (InvalidParamsError const& err) 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 } // namespace RPC

View File

@@ -20,16 +20,23 @@
#pragma once #pragma once
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <config/Config.h>
#include <log/Logger.h> #include <log/Logger.h>
#include <rpc/Counters.h> #include <rpc/Counters.h>
#include <rpc/Errors.h> #include <rpc/Errors.h>
#include <rpc/HandlerTable.h>
#include <rpc/common/AnyHandler.h>
#include <util/Taggable.h> #include <util/Taggable.h>
#include <webserver/Context.h>
#include <webserver/DOSGuard.h>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <fmt/core.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <unordered_map>
#include <variant> #include <variant>
/* /*
@@ -50,41 +57,6 @@ class ReportingETL;
namespace RPC { 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<BackendInterface const> 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<SubscriptionManager> subscriptions;
std::shared_ptr<ETLLoadBalancer> const& balancer;
std::shared_ptr<ReportingETL const> const& etl;
std::shared_ptr<WsBase> 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<BackendInterface const> const& backend_,
std::shared_ptr<SubscriptionManager> const& subscriptions_,
std::shared_ptr<ETLLoadBalancer> const& balancer_,
std::shared_ptr<ReportingETL const> const& etl_,
std::shared_ptr<WsBase> const& session_,
util::TagDecoratorFactory const& tagFactory_,
Backend::LedgerRange const& range_,
Counters& counters_,
std::string const& clientIp_);
};
struct AccountCursor struct AccountCursor
{ {
ripple::uint256 index; ripple::uint256 index;
@@ -105,61 +77,131 @@ struct AccountCursor
using Result = std::variant<Status, boost::json::object>; using Result = std::variant<Status, boost::json::object>;
std::optional<Context> std::optional<Web::Context>
make_WsContext( make_WsContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl,
std::shared_ptr<WsBase> const& session, std::shared_ptr<WsBase> const& session,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
Counters& counters,
std::string const& clientIp); std::string const& clientIp);
std::optional<Context> std::optional<Web::Context>
make_HttpContext( make_HttpContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
Counters& counters,
std::string const& clientIp); 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<BackendInterface> backend_;
std::shared_ptr<SubscriptionManager> subscriptions_;
std::shared_ptr<ETLLoadBalancer> balancer_;
std::reference_wrapper<clio::DOSGuard const> dosGuard_;
std::reference_wrapper<WorkQueue> workQueue_;
std::reference_wrapper<Counters> counters_;
bool HandlerTable handlerTable_;
validHandler(std::string const& method);
bool public:
isClioOnly(std::string const& method); RPCEngine(
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL> const& etl,
clio::DOSGuard const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> const& handlerProvider);
Status static std::shared_ptr<RPCEngine>
getLimit(RPC::Context const& context, std::uint32_t& limit); make_RPCEngine(
clio::Config const& config,
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL> const& etl,
clio::DOSGuard const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> 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 <typename Fn>
bool
post(Fn&& func, std::string const& ip)
{
return workQueue_.get().postCoro(std::forward<Fn>(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 <class T> template <class T>
void 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"}; static clio::Logger log{"RPC"};
std::stringstream ss; auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
ss << ctx.tag() auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
<< "Request processing duration = " << std::chrono::duration_cast<std::chrono::milliseconds>(dur).count() auto const msg =
<< " milliseconds. request = " << ctx.params; fmt::format("Request processing duration = {} milliseconds. request = {}", millis, serialize(ctx.params));
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
if (seconds > 10) if (seconds > 10)
log.error() << ss.str(); log.error() << ctx.tag() << msg;
else if (seconds > 1) else if (seconds > 1)
log.warn() << ss.str(); log.warn() << ctx.tag() << msg;
else else
log.info() << ss.str(); log.info() << ctx.tag() << msg;
} }
} // namespace RPC } // namespace RPC

View File

@@ -20,6 +20,8 @@
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <log/Logger.h> #include <log/Logger.h>
#include <rpc/Errors.h>
#include <rpc/RPC.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <util/Profiler.h> #include <util/Profiler.h>
@@ -484,7 +486,7 @@ parseStringAsUInt(std::string const& value)
} }
std::variant<Status, ripple::LedgerInfo> std::variant<Status, ripple::LedgerInfo>
ledgerInfoFromRequest(Context const& ctx) ledgerInfoFromRequest(std::shared_ptr<Backend::BackendInterface const> const& backend, Web::Context const& ctx)
{ {
auto hashValue = ctx.params.contains("ledger_hash") ? ctx.params.at("ledger_hash") : nullptr; 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())) if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
return Status{RippledError::rpcINVALID_PARAMS, "ledgerHashMalformed"}; 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) if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
@@ -529,7 +531,7 @@ ledgerInfoFromRequest(Context const& ctx)
if (!ledgerSequence) if (!ledgerSequence)
return Status{RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; 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) if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
@@ -547,7 +549,7 @@ getLedgerInfoFromHashOrSeq(
uint32_t maxSeq) uint32_t maxSeq)
{ {
std::optional<ripple::LedgerInfo> lgrInfo; std::optional<ripple::LedgerInfo> lgrInfo;
auto const err = RPC::Status{RPC::RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; auto const err = Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
if (ledgerHash) if (ledgerHash)
{ {
// invoke uint256's constructor to parse the hex string , instead of // invoke uint256's constructor to parse the hex string , instead of
@@ -809,9 +811,13 @@ traverseOwnedNodes(
} }
std::shared_ptr<ripple::SLE const> std::shared_ptr<ripple::SLE const>
read(ripple::Keylet const& keylet, ripple::LedgerInfo const& lgrInfo, Context const& context) read(
std::shared_ptr<Backend::BackendInterface const> 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::SLE const>(ripple::SerialIter{blob->data(), blob->size()}, keylet.key); return std::make_shared<ripple::SLE const>(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. // is. Split it out into some helper functions.
std::variant<Status, boost::json::object> std::variant<Status, boost::json::object>
traverseTransactions( traverseTransactions(
Context const& context, std::shared_ptr<Backend::BackendInterface const> const& backend,
Web::Context const& context,
std::function<Backend::TransactionsAndCursor( std::function<Backend::TransactionsAndCursor(
std::shared_ptr<Backend::BackendInterface const> const& backend, std::shared_ptr<Backend::BackendInterface const> const& backend,
std::uint32_t const,
bool const, bool const,
std::optional<Backend::TransactionsCursor> const&, std::optional<Backend::TransactionsCursor> const&,
boost::asio::yield_context& yield)> transactionFetcher) boost::asio::yield_context& yield)> transactionFetcher)
@@ -1564,7 +1570,7 @@ traverseTransactions(
if (request.contains(JS(ledger_index_max)) || request.contains(JS(ledger_index_min))) if (request.contains(JS(ledger_index_max)) || request.contains(JS(ledger_index_min)))
return Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}; return Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"};
auto v = ledgerInfoFromRequest(context); auto v = ledgerInfoFromRequest(backend, context);
if (auto status = std::get_if<Status>(&v); status) if (auto status = std::get_if<Status>(&v); status)
return *status; return *status;
@@ -1579,15 +1585,8 @@ traverseTransactions(
cursor = {maxIndex, INT32_MAX}; 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; 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]() { auto timeDiff = util::timed([&, &retCursor = retCursor, &blobs = blobs]() {
if (retCursor) if (retCursor)
{ {

View File

@@ -28,18 +28,13 @@
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
#include <ripple/protocol/jss.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/JS.h>
#include <rpc/RPC.h> #include <rpc/RPC.h>
#include <webserver/Context.h>
// 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()
namespace RPC { namespace RPC {
std::optional<ripple::AccountID> std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account); accountFromStringStrict(std::string const& account);
@@ -94,7 +89,7 @@ generatePubLedgerMessage(
std::uint32_t txnCount); std::uint32_t txnCount);
std::variant<Status, ripple::LedgerInfo> std::variant<Status, ripple::LedgerInfo>
ledgerInfoFromRequest(Context const& ctx); ledgerInfoFromRequest(std::shared_ptr<Backend::BackendInterface const> const& backend, Web::Context const& ctx);
std::variant<Status, ripple::LedgerInfo> std::variant<Status, ripple::LedgerInfo>
getLedgerInfoFromHashOrSeq( getLedgerInfoFromHashOrSeq(
@@ -139,7 +134,11 @@ ngTraverseOwnedNodes(
std::function<void(ripple::SLE&&)> atOwnedNode); std::function<void(ripple::SLE&&)> atOwnedNode);
std::shared_ptr<ripple::SLE const> std::shared_ptr<ripple::SLE const>
read(ripple::Keylet const& keylet, ripple::LedgerInfo const& lgrInfo, Context const& context); read(
std::shared_ptr<Backend::BackendInterface const> const& backend,
ripple::Keylet const& keylet,
ripple::LedgerInfo const& lgrInfo,
Web::Context const& context);
std::variant<Status, std::pair<ripple::PublicKey, ripple::SecretKey>> std::variant<Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
keypairFromRequst(boost::json::object const& request); keypairFromRequst(boost::json::object const& request);
@@ -274,15 +273,12 @@ getNFTID(boost::json::object const& request);
// be used for any future transaction enumeration APIs. // be used for any future transaction enumeration APIs.
std::variant<Status, boost::json::object> std::variant<Status, boost::json::object>
traverseTransactions( traverseTransactions(
Context const& context, std::shared_ptr<Backend::BackendInterface const> const& backend,
Web::Context const& context,
std::function<Backend::TransactionsAndCursor( std::function<Backend::TransactionsAndCursor(
std::shared_ptr<Backend::BackendInterface const> const& backend, std::shared_ptr<Backend::BackendInterface const> const& backend,
std::uint32_t const,
bool const, bool const,
std::optional<Backend::TransactionsCursor> const&, std::optional<Backend::TransactionsCursor> const&,
boost::asio::yield_context& yield)> transactionFetcher); boost::asio::yield_context& yield)> transactionFetcher);
[[nodiscard]] boost::json::object const
computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector<Backend::TransactionAndMetadata> const& transactions);
} // namespace RPC } // namespace RPC

View File

@@ -23,8 +23,7 @@ WorkQueue::WorkQueue(std::uint32_t numWorkers, uint32_t maxSize)
{ {
if (maxSize != 0) if (maxSize != 0)
maxSize_ = maxSize; maxSize_ = maxSize;
while (--numWorkers) while (--numWorkers)
{
threads_.emplace_back([this] { ioc_.run(); }); threads_.emplace_back([this] { ioc_.run(); });
}
} }

View File

@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <config/Config.h>
#include <log/Logger.h> #include <log/Logger.h>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@@ -41,9 +42,25 @@ class WorkQueue
uint32_t maxSize_ = std::numeric_limits<uint32_t>::max(); uint32_t maxSize_ = std::numeric_limits<uint32_t>::max();
clio::Logger log_{"RPC"}; clio::Logger log_{"RPC"};
std::vector<std::thread> threads_ = {};
boost::asio::io_context ioc_ = {};
std::optional<boost::asio::io_context::work> work_{ioc_};
public: public:
WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0); 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<uint32_t>("workers", std::thread::hardware_concurrency());
auto const maxQueueSize = serverConfig.valueOr<uint32_t>("max_queue_size", 0); // 0 is no limit
log.info() << "Number of workers = " << numThreads << ". Max queue size = " << maxQueueSize;
return WorkQueue{numThreads, maxQueueSize};
}
template <typename F> template <typename F>
bool bool
postCoro(F&& f, bool isWhiteListed) postCoro(F&& f, bool isWhiteListed)
@@ -53,38 +70,39 @@ public:
log_.warn() << "Queue is full. rejecting job. current size = " << curSize_ << " max size = " << maxSize_; log_.warn() << "Queue is full. rejecting job. current size = " << curSize_ << " max size = " << maxSize_;
return false; return false;
} }
++curSize_; ++curSize_;
auto start = std::chrono::system_clock::now(); 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. // Each time we enqueue a job, we want to post a symmetrical job that will dequeue and run the job at the front
boost::asio::spawn(ioc_, [this, f = std::move(f), start](boost::asio::yield_context yield) { // of the job queue.
auto run = std::chrono::system_clock::now(); boost::asio::spawn(ioc_, [this, f = std::move(f), start](auto yield) {
auto wait = std::chrono::duration_cast<std::chrono::microseconds>(run - start).count(); auto const run = std::chrono::system_clock::now();
// increment queued_ here, in the same place we implement auto const wait = std::chrono::duration_cast<std::chrono::microseconds>(run - start).count();
// durationUs_
// increment queued_ here, in the same place we implement durationUs_
++queued_; ++queued_;
durationUs_ += wait; durationUs_ += wait;
log_.info() << "WorkQueue wait time = " << wait << " queue size = " << curSize_; log_.info() << "WorkQueue wait time = " << wait << " queue size = " << curSize_;
f(yield); f(yield);
--curSize_; --curSize_;
}); });
return true; return true;
} }
boost::json::object boost::json::object
report() const report() const
{ {
boost::json::object obj; auto obj = boost::json::object{};
obj["queued"] = queued_; obj["queued"] = queued_;
obj["queued_duration_us"] = durationUs_; obj["queued_duration_us"] = durationUs_;
obj["current_queue_size"] = curSize_; obj["current_queue_size"] = curSize_;
obj["max_queue_size"] = maxSize_; obj["max_queue_size"] = maxSize_;
return obj; return obj;
} }
private:
std::vector<std::thread> threads_ = {};
boost::asio::io_context ioc_ = {};
std::optional<boost::asio::io_context::work> work_{ioc_};
}; };

View File

@@ -23,7 +23,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/impl/Processors.h> #include <rpc/common/impl/Processors.h>
namespace RPCng { namespace RPC {
/** /**
* @brief A type-erased Handler that can contain any (NextGen) RPC handler class * @brief A type-erased Handler that can contain any (NextGen) RPC handler class
@@ -39,10 +39,8 @@ public:
* @brief Type-erases any handler class. * @brief Type-erases any handler class.
* *
* @tparam HandlerType The real type of wrapped handler class * @tparam HandlerType The real type of wrapped handler class
* @tparam ProcessingStrategy A strategy that implements how processing of * @tparam ProcessingStrategy A strategy that implements how processing of JSON is to be done
* JSON is to be done * @param handler The handler to wrap. Required to fulfil the @ref Handler concept.
* @param handler The handler to wrap. Required to fulfil the @ref Handler
* concept.
*/ */
template <Handler HandlerType, typename ProcessingStrategy = detail::DefaultProcessor<HandlerType>> template <Handler HandlerType, typename ProcessingStrategy = detail::DefaultProcessor<HandlerType>>
/* implicit */ AnyHandler(HandlerType&& handler) /* implicit */ AnyHandler(HandlerType&& handler)
@@ -54,6 +52,7 @@ public:
AnyHandler(AnyHandler const& other) : pimpl_{other.pimpl_->clone()} AnyHandler(AnyHandler const& other) : pimpl_{other.pimpl_->clone()}
{ {
} }
AnyHandler& AnyHandler&
operator=(AnyHandler const& rhs) operator=(AnyHandler const& rhs)
{ {
@@ -61,6 +60,7 @@ public:
pimpl_.swap(copy.pimpl_); pimpl_.swap(copy.pimpl_);
return *this; return *this;
} }
AnyHandler(AnyHandler&&) = default; AnyHandler(AnyHandler&&) = default;
AnyHandler& AnyHandler&
operator=(AnyHandler&&) = default; operator=(AnyHandler&&) = default;
@@ -69,7 +69,7 @@ public:
* @brief Process incoming JSON by the stored handler * @brief Process incoming JSON by the stored handler
* *
* @param value The JSON to process * @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 [[nodiscard]] ReturnType
process(boost::json::value const& value) const process(boost::json::value const& value) const
@@ -78,11 +78,10 @@ public:
} }
/** /**
* @brief Process incoming JSON by the stored handler in a provided * @brief Process incoming JSON by the stored handler in a provided coroutine
* coroutine
* *
* @param value The JSON to process * @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 [[nodiscard]] ReturnType
process(boost::json::value const& value, Context const& ctx) const process(boost::json::value const& value, Context const& ctx) const
@@ -138,4 +137,4 @@ private:
std::unique_ptr<Concept> pimpl_; std::unique_ptr<Concept> pimpl_;
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <string> #include <string>
namespace RPCng { namespace RPC {
struct RpcSpec; struct RpcSpec;
@@ -84,4 +84,5 @@ concept Handler =
) )
and boost::json::has_value_from<typename T::Output>::value; and boost::json::has_value_from<typename T::Output>::value;
// clang-format on // clang-format on
} // namespace RPCng
} // namespace RPC

View File

@@ -21,7 +21,7 @@
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
namespace RPCng { namespace RPC {
[[nodiscard]] MaybeError [[nodiscard]] MaybeError
FieldSpec::validate(boost::json::value const& value) const FieldSpec::validate(boost::json::value const& value) const
@@ -39,4 +39,4 @@ RpcSpec::validate(boost::json::value const& value) const
return {}; return {};
} }
} // namespace RPCng } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace RPCng { namespace RPC {
/** /**
* @brief Represents a Specification for one field of an RPC command * @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 * @brief Validates the passed JSON value using the stored requirements
* *
* @param value The JSON value to validate * @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 [[nodiscard]] MaybeError
validate(boost::json::value const& value) const; 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 * @brief Validates the passed JSON value using the stored field specs
* *
* @param value The JSON value to validate * @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 [[nodiscard]] MaybeError
validate(boost::json::value const& value) const; validate(boost::json::value const& value) const;
@@ -90,4 +90,4 @@ private:
std::vector<FieldSpec> fields_; std::vector<FieldSpec> fields_;
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -27,29 +27,30 @@
class WsBase; class WsBase;
class SubscriptionManager; class SubscriptionManager;
namespace RPCng {
namespace RPC {
/** /**
* @brief Return type used for Validators that can return error but don't have * @brief Return type used for Validators that can return error but don't have
* specific value to return * specific value to return
*/ */
using MaybeError = util::Expected<void, RPC::Status>; using MaybeError = util::Expected<void, Status>;
/** /**
* @brief The type that represents just the error part of @ref MaybeError * @brief The type that represents just the error part of @ref MaybeError
*/ */
using Error = util::Unexpected<RPC::Status>; using Error = util::Unexpected<Status>;
/** /**
* @brief Return type for each individual handler * @brief Return type for each individual handler
*/ */
template <typename OutputType> template <typename OutputType>
using HandlerReturnType = util::Expected<OutputType, RPC::Status>; using HandlerReturnType = util::Expected<OutputType, Status>;
/** /**
* @brief The final return type out of RPC engine * @brief The final return type out of RPC engine
*/ */
using ReturnType = util::Expected<boost::json::value, RPC::Status>; using ReturnType = util::Expected<boost::json::value, Status>;
struct RpcSpec; struct RpcSpec;
struct FieldSpec; struct FieldSpec;
@@ -70,10 +71,27 @@ struct Context
std::string clientIp; std::string clientIp;
}; };
class AnyHandler;
class HandlerProvider
{
public:
virtual ~HandlerProvider() = default;
virtual bool
contains(std::string const& method) const = 0;
virtual std::optional<AnyHandler>
getHandler(std::string const& command) const = 0;
virtual bool
isClioOnly(std::string const& command) const = 0;
};
inline void inline void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, VoidOutput const&) tag_invoke(boost::json::value_from_tag, boost::json::value& jv, VoidOutput const&)
{ {
jv = boost::json::object{}; jv = boost::json::object{};
} }
} // namespace RPCng } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <charconv> #include <charconv>
#include <string_view> #include <string_view>
namespace RPCng::validation { namespace RPC::validation {
[[nodiscard]] MaybeError [[nodiscard]] MaybeError
Section::verify(boost::json::value const& value, std::string_view key) const 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 // instead
auto const& res = value.at(key.data()); auto const& res = value.at(key.data());
// if it is not a json object, let other validators fail // if it is not a json object, let other validators fail
if (!res.is_object()) if (!res.is_object())
return {}; return {};
for (auto const& spec : specs) for (auto const& spec : specs)
{ {
if (auto const ret = spec.validate(res); not ret) if (auto const ret = spec.validate(res); not ret)
return Error{ret.error()}; return Error{ret.error()};
} }
return {}; 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 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())) if (not value.is_object() or not value.as_object().contains(key.data()))
return Error{ return Error{Status{RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"}};
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"}};
return {}; return {};
} }
@@ -65,11 +67,11 @@ ValidateArrayAt::verify(boost::json::value const& value, std::string_view key) c
// instead // instead
if (not value.as_object().at(key.data()).is_array()) 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(); auto const& arr = value.as_object().at(key.data()).as_array();
if (idx_ >= arr.size()) if (idx_ >= arr.size())
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; return Error{Status{RippledError::rpcINVALID_PARAMS}};
auto const& res = arr.at(idx_); auto const& res = arr.at(idx_);
for (auto const& spec : specs_) for (auto const& spec : specs_)
@@ -94,107 +96,107 @@ checkIsU32Numeric(std::string_view sv)
{ {
uint32_t unused; uint32_t unused;
auto [_, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), unused); auto [_, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), unused);
return ec == std::errc(); return ec == std::errc();
} }
CustomValidator Uint256HexStringValidator = CustomValidator Uint256HexStringValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) if (!value.is_string())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
}
ripple::uint256 ledgerHash; ripple::uint256 ledgerHash;
if (!ledgerHash.parseHex(value.as_string().c_str())) 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{}; return MaybeError{};
}}; }};
CustomValidator LedgerIndexValidator = CustomValidator LedgerIndexValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { 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())) if (!value.is_string() && !(value.is_uint64() || value.is_int64()))
{
return err; return err;
}
if (value.is_string() && value.as_string() != "validated" && !checkIsU32Numeric(value.as_string().c_str())) if (value.is_string() && value.as_string() != "validated" && !checkIsU32Numeric(value.as_string().c_str()))
{
return err; return err;
}
return MaybeError{}; return MaybeError{};
}}; }};
CustomValidator AccountValidator = CustomValidator AccountValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) if (!value.is_string())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
}
// TODO: we are using accountFromStringStrict from RPCHelpers, after we // TODO: we are using accountFromStringStrict from RPCHelpers, after we
// remove all old handler, this function can be moved to here // remove all old handler, this function can be moved to here
if (!RPC::accountFromStringStrict(value.as_string().c_str())) if (!accountFromStringStrict(value.as_string().c_str()))
{ return Error{Status{RippledError::rpcACT_MALFORMED, std::string(key) + "Malformed"}};
return Error{RPC::Status{RPC::RippledError::rpcACT_MALFORMED, std::string(key) + "Malformed"}};
}
return MaybeError{}; return MaybeError{};
}}; }};
CustomValidator AccountBase58Validator = CustomValidator AccountBase58Validator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) if (!value.is_string())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
}
auto const account = ripple::parseBase58<ripple::AccountID>(value.as_string().c_str()); auto const account = ripple::parseBase58<ripple::AccountID>(value.as_string().c_str());
if (!account || account->isZero()) if (!account || account->isZero())
return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS}}; return Error{Status{ClioError::rpcMALFORMED_ADDRESS}};
return MaybeError{}; return MaybeError{};
}}; }};
CustomValidator AccountMarkerValidator = CustomValidator AccountMarkerValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) if (!value.is_string())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
}
// TODO: we are using parseAccountCursor from RPCHelpers, after we // TODO: we are using parseAccountCursor from RPCHelpers, after we
// remove all old handler, this function can be moved to here // 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 // 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{}; return MaybeError{};
}}; }};
CustomValidator CurrencyValidator = CustomValidator CurrencyValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) if (!value.is_string())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
}
ripple::Currency currency; ripple::Currency currency;
if (!ripple::to_currency(currency, value.as_string().c_str())) 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{}; return MaybeError{};
}}; }};
CustomValidator IssuerValidator = CustomValidator IssuerValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string()) 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; ripple::AccountID issuer;
// TODO: need to align with the error
if (!ripple::to_issuer(issuer, value.as_string().c_str())) if (!ripple::to_issuer(issuer, value.as_string().c_str()))
return Error{RPC::Status{// TODO: need to align with the error return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', bad issuer.", key)}};
RPC::RippledError::rpcINVALID_PARAMS,
fmt::format("Invalid field '{}', bad issuer.", key)}};
if (issuer == ripple::noAccount()) if (issuer == ripple::noAccount())
return Error{RPC::Status{ return Error{Status{
RPC::RippledError::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
fmt::format( fmt::format(
"Invalid field '{}', bad issuer account " "Invalid field '{}', bad issuer account "
"one.", "one.",
key)}}; key)}};
return MaybeError{}; return MaybeError{};
}}; }};
@@ -203,34 +205,40 @@ CustomValidator SubscribeStreamValidator =
static std::unordered_set<std::string> const validStreams = { static std::unordered_set<std::string> const validStreams = {
"ledger", "transactions", "transactions_proposed", "book_changes", "manifests", "validations"}; "ledger", "transactions", "transactions_proposed", "book_changes", "manifests", "validations"};
if (!value.is_array()) if (!value.is_array())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
}
for (auto const& v : value.as_array()) for (auto const& v : value.as_array())
{ {
if (!v.is_string()) 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())) 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{}; return MaybeError{};
}}; }};
CustomValidator SubscribeAccountsValidator = CustomValidator SubscribeAccountsValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array()) 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) 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()) for (auto const& v : value.as_array())
{ {
auto obj = boost::json::object(); auto obj = boost::json::object();
auto const keyItem = std::string(key) + "'sItem"; auto const keyItem = std::string(key) + "'sItem";
obj[keyItem] = v; obj[keyItem] = v;
if (auto const err = AccountValidator.verify(obj, keyItem); !err) if (auto const err = AccountValidator.verify(obj, keyItem); !err)
return err; return err;
} }
return MaybeError{}; return MaybeError{};
}}; }};
} // namespace RPCng::validation
} // namespace RPC::validation

View File

@@ -25,7 +25,7 @@
#include <fmt/core.h> #include <fmt/core.h>
namespace RPCng::validation { namespace RPC::validation {
/** /**
* @brief Check that the type is the same as what was expected * @brief Check that the type is the same as what was expected
@@ -134,8 +134,8 @@ public:
using boost::json::value_to; using boost::json::value_to;
auto const res = value_to<T>(value.as_object().at(key.data())); auto const res = value_to<T>(value.as_object().at(key.data()));
if (value_ == res) if (value_ == res)
return Error{RPC::Status{ return Error{Status{
RPC::RippledError::rpcNOT_SUPPORTED, RippledError::rpcNOT_SUPPORTED,
fmt::format("Not supported field '{}'s value '{}'", std::string{key}, res)}}; fmt::format("Not supported field '{}'s value '{}'", std::string{key}, res)}};
} }
return {}; return {};
@@ -157,9 +157,8 @@ public:
verify(boost::json::value const& value, std::string_view key) const verify(boost::json::value const& value, std::string_view key) const
{ {
if (value.is_object() and value.as_object().contains(key.data())) if (value.is_object() and value.as_object().contains(key.data()))
{ return Error{Status{RippledError::rpcNOT_SUPPORTED, "Not supported field '" + std::string{key}}};
return Error{RPC::Status{RPC::RippledError::rpcNOT_SUPPORTED, "Not supported field '" + std::string{key}}};
}
return {}; return {};
} }
}; };
@@ -192,7 +191,7 @@ struct Type final
auto const convertible = (checkType<Types>(res) || ...); auto const convertible = (checkType<Types>(res) || ...);
if (not convertible) if (not convertible)
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; return Error{Status{RippledError::rpcINVALID_PARAMS}};
return {}; return {};
} }
@@ -228,16 +227,18 @@ public:
[[nodiscard]] MaybeError [[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const 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())) if (not value.is_object() or not value.as_object().contains(key.data()))
return {}; // ignore. field does not exist, let 'required' fail return {}; // ignore. field does not exist, let 'required' fail
// instead // instead
using boost::json::value_to;
auto const res = value_to<Type>(value.as_object().at(key.data())); auto const res = value_to<Type>(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) // function that can be overridden for this comparison)
if (res < min_ || res > max_) if (res < min_ || res > max_)
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; return Error{Status{RippledError::rpcINVALID_PARAMS}};
return {}; return {};
} }
@@ -271,14 +272,15 @@ public:
[[nodiscard]] MaybeError [[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const 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())) if (not value.is_object() or not value.as_object().contains(key.data()))
return {}; // ignore. field does not exist, let 'required' fail return {}; // ignore. field does not exist, let 'required' fail
// instead // instead
using boost::json::value_to;
auto const res = value_to<Type>(value.as_object().at(key.data())); auto const res = value_to<Type>(value.as_object().at(key.data()));
if (res != original_) if (res != original_)
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}}; return Error{Status{RippledError::rpcINVALID_PARAMS}};
return {}; return {};
} }
@@ -318,14 +320,15 @@ public:
[[nodiscard]] MaybeError [[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const 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())) if (not value.is_object() or not value.as_object().contains(key.data()))
return {}; // ignore. field does not exist, let 'required' fail return {}; // ignore. field does not exist, let 'required' fail
// instead // instead
using boost::json::value_to;
auto const res = value_to<Type>(value.as_object().at(key.data())); auto const res = value_to<Type>(value.as_object().at(key.data()));
if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_)) 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 {}; return {};
} }
@@ -387,7 +390,7 @@ public:
{ {
validator_ = [... r = std::forward<Requirements>(requirements)]( validator_ = [... r = std::forward<Requirements>(requirements)](
boost::json::value const& j, std::string_view key) -> MaybeError { boost::json::value const& j, std::string_view key) -> MaybeError {
std::optional<RPC::Status> firstFailure = std::nullopt; std::optional<Status> firstFailure = std::nullopt;
// the check logic is the same as fieldspec // the check logic is the same as fieldspec
// clang-format off // clang-format off
@@ -439,14 +442,14 @@ template <typename Requirement>
class WithCustomError final class WithCustomError final
{ {
Requirement requirement; Requirement requirement;
RPC::Status error; Status error;
public: public:
/** /**
* @brief Constructs a validator that calls the given validator "req" and * @brief Constructs a validator that calls the given validator "req" and
* return customized error "err" * 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; extern CustomValidator SubscribeAccountsValidator;
} // namespace RPCng::validation } // namespace RPC::validation

View File

@@ -26,14 +26,14 @@
#include <optional> #include <optional>
namespace RPCng::detail { namespace RPC::detail {
template <Requirement... Requirements> template <Requirement... Requirements>
[[nodiscard]] auto [[nodiscard]] auto
makeFieldValidator(std::string const& key, Requirements&&... requirements) makeFieldValidator(std::string const& key, Requirements&&... requirements)
{ {
return [key, ... r = std::forward<Requirements>(requirements)](boost::json::value const& j) -> MaybeError { return [key, ... r = std::forward<Requirements>(requirements)](boost::json::value const& j) -> MaybeError {
std::optional<RPC::Status> firstFailure = std::nullopt; std::optional<Status> firstFailure = std::nullopt;
// This expands in order of Requirements and stops evaluating after // This expands in order of Requirements and stops evaluating after
// first failure which is stored in `firstFailure` and can be checked // 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

View File

@@ -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 <rpc/common/impl/HandlerProvider.h>
#include <etl/ReportingETL.h>
#include <rpc/Counters.h>
#include <subscriptions/SubscriptionManager.h>
#include <rpc/handlers/AccountChannels.h>
#include <rpc/handlers/AccountCurrencies.h>
#include <rpc/handlers/AccountInfo.h>
#include <rpc/handlers/AccountLines.h>
#include <rpc/handlers/AccountNFTs.h>
#include <rpc/handlers/AccountObjects.h>
#include <rpc/handlers/AccountOffers.h>
#include <rpc/handlers/AccountTx.h>
#include <rpc/handlers/BookChanges.h>
#include <rpc/handlers/BookOffers.h>
#include <rpc/handlers/GatewayBalances.h>
#include <rpc/handlers/Ledger.h>
#include <rpc/handlers/LedgerData.h>
#include <rpc/handlers/LedgerEntry.h>
#include <rpc/handlers/LedgerRange.h>
#include <rpc/handlers/NFTBuyOffers.h>
#include <rpc/handlers/NFTHistory.h>
#include <rpc/handlers/NFTInfo.h>
#include <rpc/handlers/NFTSellOffers.h>
#include <rpc/handlers/NoRippleCheck.h>
#include <rpc/handlers/Ping.h>
#include <rpc/handlers/Random.h>
#include <rpc/handlers/ServerInfo.h>
#include <rpc/handlers/Subscribe.h>
#include <rpc/handlers/TransactionEntry.h>
#include <rpc/handlers/Tx.h>
#include <rpc/handlers/Unsubscribe.h>
namespace RPC::detail {
ProductionHandlerProvider::ProductionHandlerProvider(
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptionManager,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> 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<AnyHandler>
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

View File

@@ -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 <backend/BackendInterface.h>
#include <rpc/common/AnyHandler.h>
#include <rpc/common/Types.h>
#include <subscriptions/SubscriptionManager.h>
#include <optional>
#include <string>
#include <unordered_map>
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<std::string, Handler> handlerMap_;
public:
ProductionHandlerProvider(
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptionManager,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl,
Counters const& counters);
bool
contains(std::string const& method) const override;
std::optional<AnyHandler>
getHandler(std::string const& command) const override;
bool
isClioOnly(std::string const& command) const override;
};
} // namespace RPC::detail

View File

@@ -22,7 +22,7 @@
#include <rpc/common/Concepts.h> #include <rpc/common/Concepts.h>
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
namespace RPCng::detail { namespace RPC::detail {
template <typename> template <typename>
static constexpr bool unsupported_handler_v = false; static constexpr bool unsupported_handler_v = false;
@@ -91,4 +91,4 @@ struct DefaultProcessor final
} }
}; };
} // namespace RPCng::detail } // namespace RPC::detail

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,118 +17,170 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/handlers/AccountChannels.h>
namespace RPC { namespace RPC {
void void
addChannel(boost::json::array& jsonLines, ripple::SLE const& line) AccountChannelsHandler::addChannel(std::vector<ChannelResponse>& jsonChannels, ripple::SLE const& channelSle) const
{ {
boost::json::object jDst; ChannelResponse channel;
jDst[JS(channel_id)] = ripple::to_string(line.key()); channel.channelID = ripple::to_string(channelSle.key());
jDst[JS(account)] = ripple::to_string(line.getAccountID(ripple::sfAccount)); channel.account = ripple::to_string(channelSle.getAccountID(ripple::sfAccount));
jDst[JS(destination_account)] = ripple::to_string(line.getAccountID(ripple::sfDestination)); channel.accountDestination = ripple::to_string(channelSle.getAccountID(ripple::sfDestination));
jDst[JS(amount)] = line[ripple::sfAmount].getText(); channel.amount = channelSle[ripple::sfAmount].getText();
jDst[JS(balance)] = line[ripple::sfBalance].getText(); channel.balance = channelSle[ripple::sfBalance].getText();
if (publicKeyType(line[ripple::sfPublicKey])) channel.settleDelay = channelSle[ripple::sfSettleDelay];
{
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;
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 AccountChannelsHandler::Result
doAccountChannels(Context const& context) AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context const& ctx) const
{ {
auto request = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context); if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
if (auto status = std::get_if<Status>(&v)) return Error{*status};
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(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 (!accountLedgerObject)
if (auto const status = getAccount(request, accountID); status) return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
return status;
auto rawAcct = auto const destAccountID = input.destinationAccount ? accountFromStringStrict(input.destinationAccount.value())
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); : std::optional<ripple::AccountID>{};
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<std::string> 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();
Output response;
auto const addToResponse = [&](ripple::SLE&& sle) { auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltPAYCHAN && sle.getAccountID(ripple::sfAccount) == accountID && 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; return true;
}; };
auto next = auto const next = ngTraverseOwnedNodes(
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response.account = input.account;
response[JS(ledger_index)] = lgrInfo.seq; response.limit = input.limit;
response.ledgerHash = ripple::strHex(lgrInfo.hash);
if (auto status = std::get_if<RPC::Status>(&next)) response.ledgerIndex = lgrInfo.seq;
return *status;
auto nextMarker = std::get<RPC::AccountCursor>(next);
auto const nextMarker = std::get<AccountCursor>(next);
if (nextMarker.isNonZero()) if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString(); response.marker = nextMarker.toString();
return response; return response;
} }
AccountChannelsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, 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 } // namespace RPC

View File

@@ -20,13 +20,13 @@
#pragma once #pragma once
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/JS.h>
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
#include <vector> #include <vector>
namespace RPCng { namespace RPC {
class AccountChannelsHandler class AccountChannelsHandler
{ {
// dependencies // dependencies
@@ -72,7 +72,7 @@ public:
std::optional<std::string> marker; std::optional<std::string> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountChannelsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) AccountChannelsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -110,4 +110,4 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ChannelResponse const& channel); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ChannelResponse const& channel);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,90 +17,99 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h> #include <rpc/handlers/AccountCurrencies.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
AccountCurrenciesHandler::Result
Result AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context const& ctx) const
doAccountCurrencies(Context const& context)
{ {
auto request = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context); if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
if (auto status = std::get_if<Status>(&v)) return Error{*status};
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const accountID = accountFromStringStrict(input.account);
ripple::AccountID accountID; auto const accountLedgerObject =
if (auto const status = getAccount(request, accountID); status) sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield);
return status; if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto rawAcct = Output response;
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::set<std::string> send, receive;
auto const addToResponse = [&](ripple::SLE&& sle) { auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE) 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) if (!viewLowest)
balance.negate(); balance.negate();
if (balance < lineLimit) if (balance < lineLimit)
receive.insert(ripple::to_string(balance.getCurrency())); response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency()));
if ((-balance) < lineLimitPeer) if ((-balance) < lineLimitPeer)
send.insert(ripple::to_string(balance.getCurrency())); response.sendCurrencies.insert(ripple::to_string(balance.getCurrency()));
} }
return true; return true;
}; };
traverseOwnedNodes( // traverse all owned nodes, limit->max, marker->empty
*context.backend, ngTraverseOwnedNodes(
accountID, *sharedPtrBackend_,
*accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
context.yield, ctx.yield,
addToResponse); addToResponse);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response.ledgerHash = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq; response.ledgerIndex = 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());
return response; 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<AccountCurrenciesHandler::Input>, 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 } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <set> #include <set>
namespace RPCng { namespace RPC {
class AccountCurrenciesHandler class AccountCurrenciesHandler
{ {
// dependencies // dependencies
@@ -43,7 +43,7 @@ public:
bool validated = true; 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 struct Input
{ {
std::string account; std::string account;
@@ -51,7 +51,7 @@ public:
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountCurrenciesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) AccountCurrenciesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -80,4 +80,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,98 +17,110 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/protocol/Indexes.h> #include <rpc/handlers/AccountInfo.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <boost/json.hpp>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
// {
// account: <ident>,
// strict: <bool> // optional (default false)
// // if true only allow public keys and addresses.
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// signer_lists : <bool> // optional (default false)
// // if true return SignerList(s).
// queue : <bool> // 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.
// }
namespace RPC { namespace RPC {
AccountInfoHandler::Result
Result AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) const
doAccountInfo(Context const& context)
{ {
auto request = context.params; if (!input.account && !input.ident)
boost::json::object response = {}; return Error{Status{RippledError::rpcACT_MALFORMED}};
std::string strIdent; auto const range = sharedPtrBackend_->fetchLedgerRange();
if (request.contains(JS(account))) auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
strIdent = request.at(JS(account)).as_string().c_str(); *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
else if (request.contains(JS(ident)))
strIdent = request.at(JS(ident)).as_string().c_str();
else
return Status{RippledError::rpcACT_MALFORMED};
// We only need to fetch the ledger header because the ledger hash is if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
// supposed to be included in the response. The ledger sequence is specified return Error{*status};
// in the request
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(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. if (!accountLedgerObject)
auto accountID = accountFromStringStrict(strIdent); return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
if (!accountID)
return Status{RippledError::rpcACT_MALFORMED};
auto key = ripple::keylet::account(accountID.value()); ripple::STLedgerEntry const sle{
std::optional<std::vector<unsigned char>> dbResponse = ripple::SerialIter{accountLedgerObject->data(), accountLedgerObject->size()}, accountKeylet.key};
context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield);
if (!dbResponse) if (!accountKeylet.check(sle))
return Status{RippledError::rpcACT_NOT_FOUND}; return Error{Status{RippledError::rpcDB_DESERIALIZATION}};
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;
// Return SignerList(s) if that is requested. // 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 // We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account. // future when we support multiple signer lists on one account.
boost::json::array signerList; auto const signersKey = ripple::keylet::signers(*accountID);
auto signersKey = ripple::keylet::signers(*accountID);
// This code will need to be revisited if in the future we // This code will need to be revisited if in the future we
// support multiple SignerLists on one account. // 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<ripple::STLedgerEntry> signerList;
if (signers) if (signers)
{ {
ripple::STLedgerEntry sleSigners{ripple::SerialIter{signers->data(), signers->size()}, signersKey.key}; ripple::STLedgerEntry const sleSigners{
if (!signersKey.check(sleSigners)) ripple::SerialIter{signers->data(), signers->size()}, signersKey.key};
return Status{RippledError::rpcDB_DESERIALIZATION};
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<AccountInfoHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class AccountInfoHandler class AccountInfoHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -68,7 +68,7 @@ public:
bool signerLists = false; bool signerLists = false;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountInfoHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) AccountInfoHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -98,4 +98,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,146 +17,107 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h> #include <rpc/RPC.h>
#include <ripple/app/paths/TrustLine.h> #include <rpc/handlers/AccountLines.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
void void
addLine( AccountLinesHandler::addLine(
boost::json::array& jsonLines, std::vector<LineResponse>& lines,
ripple::SLE const& line, ripple::SLE const& lineSle,
ripple::AccountID const& account, ripple::AccountID const& account,
std::optional<ripple::AccountID> const& peerAccount) std::optional<ripple::AccountID> const& peerAccount) const
{ {
auto flags = line.getFieldU32(ripple::sfFlags); auto const flags = lineSle.getFieldU32(ripple::sfFlags);
auto lowLimit = line.getFieldAmount(ripple::sfLowLimit); auto const lowLimit = lineSle.getFieldAmount(ripple::sfLowLimit);
auto highLimit = line.getFieldAmount(ripple::sfHighLimit); auto const highLimit = lineSle.getFieldAmount(ripple::sfHighLimit);
auto lowID = lowLimit.getIssuer(); auto const lowID = lowLimit.getIssuer();
auto highID = highLimit.getIssuer(); auto const highID = highLimit.getIssuer();
auto lowQualityIn = line.getFieldU32(ripple::sfLowQualityIn); auto const lowQualityIn = lineSle.getFieldU32(ripple::sfLowQualityIn);
auto lowQualityOut = line.getFieldU32(ripple::sfLowQualityOut); auto const lowQualityOut = lineSle.getFieldU32(ripple::sfLowQualityOut);
auto highQualityIn = line.getFieldU32(ripple::sfHighQualityIn); auto const highQualityIn = lineSle.getFieldU32(ripple::sfHighQualityIn);
auto highQualityOut = line.getFieldU32(ripple::sfHighQualityOut); auto const highQualityOut = lineSle.getFieldU32(ripple::sfHighQualityOut);
auto balance = line.getFieldAmount(ripple::sfBalance); auto balance = lineSle.getFieldAmount(ripple::sfBalance);
bool viewLowest = (lowID == account); auto const viewLowest = (lowID == account);
auto lineLimit = viewLowest ? lowLimit : highLimit; auto const lineLimit = viewLowest ? lowLimit : highLimit;
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit; auto const lineLimitPeer = not viewLowest ? lowLimit : highLimit;
auto lineAccountIDPeer = !viewLowest ? lowID : highID; auto const lineAccountIDPeer = not viewLowest ? lowID : highID;
auto lineQualityIn = viewLowest ? lowQualityIn : highQualityIn; auto const lineQualityIn = viewLowest ? lowQualityIn : highQualityIn;
auto lineQualityOut = viewLowest ? lowQualityOut : highQualityOut; auto const lineQualityOut = viewLowest ? lowQualityOut : highQualityOut;
if (peerAccount && peerAccount != lineAccountIDPeer) if (peerAccount && peerAccount != lineAccountIDPeer)
return; return;
if (!viewLowest) if (not viewLowest)
balance.negate(); balance.negate();
bool lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); bool const lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool lineAuthPeer = flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth); bool const lineAuthPeer = flags & (not viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); bool const lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool lineNoRipplePeer = flags & (!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple); bool const lineNoRipplePeer = flags & (not viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); bool const lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
bool lineFreezePeer = flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze); bool const lineFreezePeer = flags & (not viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
ripple::STAmount const& saBalance(balance); ripple::STAmount const& saBalance = balance;
ripple::STAmount const& saLimit(lineLimit); ripple::STAmount const& saLimit = lineLimit;
ripple::STAmount const& saLimitPeer(lineLimitPeer); 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) if (lineAuth)
jPeer[JS(authorized)] = true; line.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;
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 AccountLinesHandler::Result
doAccountLines(Context const& context) AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ctx) const
{ {
auto request = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context); if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
if (auto status = std::get_if<Status>(&v)) return Error{*status};
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(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 (not accountLedgerObject)
if (auto const status = getAccount(request, accountID); status) return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
return status;
auto rawAcct = auto const peerAccountID = input.peer ? accountFromStringStrict(*(input.peer)) : std::optional<ripple::AccountID>{};
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) Output response;
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; response.lines.reserve(input.limit);
std::optional<ripple::AccountID> peerAccount; auto const addToResponse = [&](ripple::SLE&& sle) {
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<std::string> 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 {
if (sle.getType() == ripple::ltRIPPLE_STATE) if (sle.getType() == ripple::ltRIPPLE_STATE)
{ {
auto ignore = false; auto ignore = false;
if (ignoreDefault) if (input.ignoreDefault)
{ {
if (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() == accountID) if (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() == accountID)
ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfLowReserve); ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfLowReserve);
@@ -164,23 +125,109 @@ doAccountLines(Context const& context)
ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfHighReserve); ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfHighReserve);
} }
if (!ignore) if (not ignore)
addLine(jsonLines, sle, accountID, peerAccount); addLine(response.lines, sle, *accountID, peerAccountID);
} }
}; };
auto next = auto const next = ngTraverseOwnedNodes(
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
auto const nextMarker = std::get<AccountCursor>(next);
if (auto status = std::get_if<RPC::Status>(&next)) response.account = input.account;
return *status; response.limit = input.limit; // not documented,
// https://github.com/XRPLF/xrpl-dev-portal/issues/1838
auto nextMarker = std::get<RPC::AccountCursor>(next); response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
if (nextMarker.isNonZero()) if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString(); response.marker = nextMarker.toString();
return response; return response;
} }
AccountLinesHandler::Input
tag_invoke(boost::json::value_to_tag<AccountLinesHandler::Input>, 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 } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <vector> #include <vector>
namespace RPCng { namespace RPC {
class AccountLinesHandler class AccountLinesHandler
{ {
@@ -74,7 +74,7 @@ public:
std::optional<std::string> marker; std::optional<std::string> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountLinesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) AccountLinesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -117,4 +117,5 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LineResponse const& line); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LineResponse const& line);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -17,47 +17,46 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/ngHandlers/AccountNFTs.h> #include <rpc/handlers/AccountNFTs.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
namespace RPCng { namespace RPC {
AccountNFTsHandler::Result AccountNFTsHandler::Result
AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) const AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) const
{ {
auto const range = sharedPtrBackend_->fetchLedgerRange(); auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq( auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (auto const status = std::get_if<RPC::Status>(&lgrInfoOrStatus)) if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status}; return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus); auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const accountID = accountFromStringStrict(input.account);
auto const accountID = RPC::accountFromStringStrict(input.account);
auto const accountLedgerObject = auto const accountLedgerObject =
sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); 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.account = input.account;
response.limit = input.limit; response.limit = input.limit;
response.ledgerHash = ripple::strHex(lgrInfo.hash); response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq; response.ledgerIndex = lgrInfo.seq;
// if a marker was passed, start at the page specified in marker. Else, // if a marker was passed, start at the page specified in marker. Else, start at the max page
// start at the max page
auto const pageKey = auto const pageKey =
input.marker ? ripple::uint256{input.marker->c_str()} : ripple::keylet::nftpage_max(*accountID).key; input.marker ? ripple::uint256{input.marker->c_str()} : ripple::keylet::nftpage_max(*accountID).key;
auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield); auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield);
if (!blob) if (!blob)
return response; return response;
std::optional<ripple::SLE const> page{ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}}; std::optional<ripple::SLE const> page{ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}};
auto numPages = 0; auto numPages = 0u;
while (page) while (page)
{ {
@@ -67,7 +66,7 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx)
{ {
auto const nftokenID = nft[ripple::sfNFTokenID]; 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(); auto& obj = response.nfts.back().as_object();
// Pull out the components of the nft ID. // 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); response.marker = to_string(nextKey.key);
return response; 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}); page.emplace(ripple::SLE{ripple::SerialIter{nextBlob->data(), nextBlob->size()}, nextKey.key});
} }
else else
@@ -98,6 +97,7 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx)
page.reset(); page.reset();
} }
} }
return response; return response;
} }
@@ -110,7 +110,9 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountNFTsHandl
{JS(validated), output.validated}, {JS(validated), output.validated},
{JS(account), output.account}, {JS(account), output.account},
{JS(account_nfts), output.nfts}, {JS(account_nfts), output.nfts},
{JS(limit), output.limit}}; {JS(limit), output.limit},
};
if (output.marker) if (output.marker)
jv.as_object()[JS(marker)] = *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 AccountNFTsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::value const& jv) tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::value const& jv)
{ {
auto input = AccountNFTsHandler::Input{};
auto const& jsonObject = jv.as_object(); auto const& jsonObject = jv.as_object();
AccountNFTsHandler::Input input;
input.account = jsonObject.at(JS(account)).as_string().c_str(); input.account = jsonObject.at(JS(account)).as_string().c_str();
if (jsonObject.contains(JS(ledger_hash))) if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str();
@@ -131,6 +135,7 @@ tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::va
else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") else if (jsonObject.at(JS(ledger_index)).as_string() != "validated")
input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str());
} }
if (jsonObject.contains(JS(limit))) if (jsonObject.contains(JS(limit)))
input.limit = jsonObject.at(JS(limit)).as_int64(); input.limit = jsonObject.at(JS(limit)).as_int64();
@@ -140,4 +145,4 @@ tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::va
return input; return input;
} }
} // namespace RPCng } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class AccountNFTsHandler class AccountNFTsHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -51,7 +51,7 @@ public:
std::optional<std::string> marker; std::optional<std::string> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountNFTsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) AccountNFTsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -81,4 +81,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,25 +17,12 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h> #include <rpc/handlers/AccountObjects.h>
#include <ripple/app/paths/TrustLine.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/nftPageMask.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
namespace RPC { namespace RPC {
std::unordered_map<std::string, ripple::LedgerEntryType> types{ // document does not mention nft_page, we still support it tho
std::unordered_map<std::string, ripple::LedgerEntryType> const AccountObjectsHandler::TYPESMAP{
{"state", ripple::ltRIPPLE_STATE}, {"state", ripple::ltRIPPLE_STATE},
{"ticket", ripple::ltTICKET}, {"ticket", ripple::ltTICKET},
{"signer_list", ripple::ltSIGNER_LIST}, {"signer_list", ripple::ltSIGNER_LIST},
@@ -45,170 +32,102 @@ std::unordered_map<std::string, ripple::LedgerEntryType> types{
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH}, {"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
{"check", ripple::ltCHECK}, {"check", ripple::ltCHECK},
{"nft_page", ripple::ltNFTOKEN_PAGE}, {"nft_page", ripple::ltNFTOKEN_PAGE},
{"nft_offer", ripple::ltNFTOKEN_OFFER}}; {"nft_offer", ripple::ltNFTOKEN_OFFER},
};
Result AccountObjectsHandler::Result
doAccountNFTs(Context const& context) AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const& ctx) const
{ {
auto request = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context); if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
if (auto status = std::get_if<Status>(&v)) return Error{*status};
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(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 (!accountLedgerObject)
if (auto const status = getAccount(request, accountID); status) return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
return status;
if (!accountID) Output response;
return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"}; auto const addToResponse = [&](ripple::SLE&& sle) {
if (!input.type || sle.getType() == *(input.type))
response.accountObjects.push_back(std::move(sle));
return true;
};
auto rawAcct = auto const next = ngTraverseOwnedNodes(
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (!rawAcct) if (auto status = std::get_if<Status>(&next))
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}; return Error{*status};
std::uint32_t limit; response.ledgerHash = ripple::strHex(lgrInfo.hash);
if (auto const status = getLimit(context, limit); status) response.ledgerIndex = lgrInfo.seq;
return status; response.limit = input.limit;
response.account = input.account;
ripple::uint256 marker; auto const& nextMarker = std::get<AccountCursor>(next);
if (auto const status = getHexMarker(request, marker); status)
return status;
response[JS(account)] = ripple::toBase58(accountID); if (nextMarker.isNonZero())
response[JS(validated)] = true; response.marker = nextMarker.toString();
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<ripple::SLE const> 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();
}
return response; return response;
} }
Result void
doAccountObjects(Context const& context) tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHandler::Output const& output)
{ {
auto request = context.params; auto objects = boost::json::array{};
boost::json::object response = {}; for (auto const& sle : output.accountObjects)
objects.push_back(toJson(sle));
auto v = ledgerInfoFromRequest(context); jv = {
if (auto status = std::get_if<Status>(&v)) {JS(ledger_hash), output.ledgerHash},
return *status; {JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
auto lgrInfo = std::get<ripple::LedgerInfo>(v); {JS(limit), output.limit},
{JS(account), output.account},
ripple::AccountID accountID; {JS(account_objects), objects},
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<std::string> 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<ripple::LedgerEntryType> 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));
}
}; };
auto next = if (output.marker)
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); jv.as_object()[JS(marker)] = *(output.marker);
}
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); AccountObjectsHandler::Input
response[JS(ledger_index)] = lgrInfo.seq; tag_invoke(boost::json::value_to_tag<AccountObjectsHandler::Input>, boost::json::value const& jv)
{
auto input = AccountObjectsHandler::Input{};
auto const& jsonObject = jv.as_object();
if (auto status = std::get_if<RPC::Status>(&next)) input.account = jv.at(JS(account)).as_string().c_str();
return *status;
auto const& nextMarker = std::get<RPC::AccountCursor>(next); if (jsonObject.contains(JS(ledger_hash)))
if (nextMarker.isNonZero()) input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str();
response[JS(marker)] = nextMarker.toString();
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 } // namespace RPC

View File

@@ -26,11 +26,13 @@
#include <set> #include <set>
namespace RPCng { namespace RPC {
class AccountObjectsHandler class AccountObjectsHandler
{ {
// dependencies // dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
// constants
static std::unordered_map<std::string, ripple::LedgerEntryType> const TYPESMAP; static std::unordered_map<std::string, ripple::LedgerEntryType> const TYPESMAP;
public: public:
@@ -51,12 +53,12 @@ public:
std::string account; std::string account;
std::optional<std::string> ledgerHash; std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
uint32_t limit = 200; //[10,400] uint32_t limit = 200; // [10,400]
std::optional<std::string> marker; std::optional<std::string> marker;
std::optional<ripple::LedgerEntryType> type; std::optional<ripple::LedgerEntryType> type;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountObjectsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) AccountObjectsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -83,7 +85,8 @@ public:
"deposit_preauth", "deposit_preauth",
"check", "check",
"nft_page", "nft_page",
"nft_offer"}}, "nft_offer",
}},
{JS(marker), validation::AccountMarkerValidator}, {JS(marker), validation::AccountMarkerValidator},
}; };
@@ -100,4 +103,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,133 +17,144 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h> #include <rpc/handlers/AccountOffers.h>
#include <ripple/app/paths/TrustLine.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
namespace RPC { namespace RPC {
void void
addOffer(boost::json::array& offersJson, ripple::SLE const& offer) AccountOffersHandler::addOffer(std::vector<Offer>& offers, ripple::SLE const& offerSle) const
{ {
auto quality = getQuality(offer.getFieldH256(ripple::sfBookDirectory)); auto offer = AccountOffersHandler::Offer();
ripple::STAmount rate = ripple::amountFromQuality(quality); 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); auto const quality = getQuality(offerSle.getFieldH256(ripple::sfBookDirectory));
ripple::STAmount takerGets = offer.getFieldAmount(ripple::sfTakerGets); 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()) offers.push_back(offer);
{
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);
}; };
Result AccountOffersHandler::Result
doAccountOffers(Context const& context) AccountOffersHandler::process(AccountOffersHandler::Input input, Context const& ctx) const
{ {
auto request = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context); if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
if (auto status = std::get_if<Status>(&v)) return Error{*status};
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto const lgrInfo = std::get<ripple::LedgerInfo>(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 (!accountLedgerObject)
if (auto const status = getAccount(request, accountID); status) return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
return status;
auto rawAcct = Output response;
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); response.account = ripple::to_string(*accountID);
response.ledgerHash = ripple::strHex(lgrInfo.hash);
if (!rawAcct) response.ledgerIndex = lgrInfo.seq;
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
std::optional<std::string> 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();
auto const addToResponse = [&](ripple::SLE&& sle) { auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltOFFER) if (sle.getType() == ripple::ltOFFER)
{ addOffer(response.offers, sle);
addOffer(jsonLines, sle);
}
return true; return true;
}; };
auto next = auto const next = ngTraverseOwnedNodes(
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (auto status = std::get_if<RPC::Status>(&next)) if (auto const status = std::get_if<Status>(&next))
return *status; return Error{*status};
auto nextMarker = std::get<RPC::AccountCursor>(next); auto const nextMarker = std::get<AccountCursor>(next);
if (nextMarker.isNonZero()) if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString(); response.marker = nextMarker.toString();
return response; 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<AccountOffersHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class AccountOffersHandler class AccountOffersHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -51,7 +51,7 @@ public:
bool validated = true; bool validated = true;
}; };
// TODO:we did not implement the "strict" field // TODO: We did not implement the "strict" field
struct Input struct Input
{ {
std::string account; std::string account;
@@ -61,7 +61,7 @@ public:
std::optional<std::string> marker; std::optional<std::string> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountOffersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) AccountOffersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -98,4 +98,4 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Offer const& offer); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Offer const& offer);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,48 +17,196 @@
*/ */
//============================================================================== //==============================================================================
#include <log/Logger.h> #include <rpc/handlers/AccountTx.h>
#include <rpc/RPCHelpers.h>
#include <util/Profiler.h> #include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
Result // TODO: this is currently very similar to nft_history but its own copy for time
doAccountTx(Context const& context) // 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; auto const range = sharedPtrBackend_->fetchLedgerRange();
if (auto const status = getAccount(context.params, accountID); status) auto [minIndex, maxIndex] = *range;
return status;
constexpr std::string_view outerFuncName = __func__; if (input.ledgerIndexMin)
auto const maybeResponse = traverseTransactions( {
context, if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin)
[&accountID, &outerFuncName]( return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
std::shared_ptr<Backend::BackendInterface const> const& backend,
std::uint32_t const limit,
bool const forward,
std::optional<Backend::TransactionsCursor> 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 (auto const status = std::get_if<Status>(&maybeResponse); status) minIndex = *input.ledgerIndexMin;
return *status; }
auto response = std::get<boost::json::object>(maybeResponse);
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<Status>(&lgrInfoOrStatus))
return Error{*status};
maxIndex = minIndex = std::get<ripple::LedgerInfo>(lgrInfoOrStatus).seq;
}
std::optional<Backend::TransactionsCursor> 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; 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<AccountTxHandler::Input>, 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 } // namespace RPC

View File

@@ -25,7 +25,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class AccountTxHandler class AccountTxHandler
{ {
clio::Logger log_{"RPC"}; clio::Logger log_{"RPC"};
@@ -67,7 +67,7 @@ public:
std::optional<Marker> marker; std::optional<Marker> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
AccountTxHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) AccountTxHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -88,7 +88,8 @@ public:
{JS(marker), {JS(marker),
validation::WithCustomError{ validation::WithCustomError{
validation::Type<boost::json::object>{}, validation::Type<boost::json::object>{},
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidMarker"}}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"},
},
validation::Section{ validation::Section{
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}}, {JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}}, {JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
@@ -111,4 +112,4 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,243 +17,79 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h> #include <rpc/handlers/BookChanges.h>
#include <ripple/basics/ToString.h>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <algorithm>
namespace json = boost::json;
using namespace ripple;
namespace RPC { namespace RPC {
/** BookChangesHandler::Result
* @brief Represents an entry in the book_changes' changes array. BookChangesHandler::process(BookChangesHandler::Input input, Context const& ctx) const
*/
struct BookChange
{ {
STAmount sideAVolume; auto const range = sharedPtrBackend_->fetchLedgerRange();
STAmount sideBVolume; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
STAmount highRate; *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
STAmount lowRate;
STAmount openRate;
STAmount closeRate;
};
/** if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
* @brief Encapsulates the book_changes computations and transformations. return Error{*status};
*/
class BookChanges final
{
public:
BookChanges() = delete; // only accessed via static handle function
/** auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
* @brief Computes all book_changes for the given transactions. auto const transactions = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield);
*
* @param transactions The transactions to compute book changes for
* @return std::vector<BookChange> Book changes
*/
[[nodiscard]] static std::vector<BookChange>
compute(std::vector<Backend::TransactionAndMetadata> const& transactions)
{
return HandlerImpl{}(transactions);
}
private: Output response;
class HandlerImpl final response.bookChanges = BookChanges::compute(transactions);
{ response.ledgerHash = ripple::strHex(lgrInfo.hash);
std::map<std::string, BookChange> tally_ = {}; response.ledgerIndex = lgrInfo.seq;
std::optional<uint32_t> offerCancel_ = {}; response.ledgerTime = lgrInfo.closeTime.time_since_epoch().count();
public: return response;
[[nodiscard]] std::vector<BookChange> }
operator()(std::vector<Backend::TransactionAndMetadata> const& transactions)
{
for (auto const& tx : transactions)
handleBookChange(tx);
// TODO: rewrite this with std::ranges when compilers catch up
std::vector<BookChange> 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<STObject>();
auto const& previousFields = node.peekAtField(sfPreviousFields).downcast<STObject>();
// 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<uint32_t>
shouldCancelOffer(std::shared_ptr<ripple::STTx const> 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;
}
}
};
};
void 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 { using boost::json::value_from;
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());
};
jv = { jv = {
{JS(currency_a), currencyStr(change.sideAVolume)}, {JS(type), "bookChanges"},
{JS(currency_b), currencyStr(change.sideBVolume)}, {JS(ledger_hash), output.ledgerHash},
{JS(volume_a), amountStr(change.sideAVolume)}, {JS(ledger_index), output.ledgerIndex},
{JS(volume_b), amountStr(change.sideBVolume)}, {JS(ledger_time), output.ledgerTime},
{JS(high), to_string(change.highRate.iou())}, {JS(validated), output.validated},
{JS(low), to_string(change.lowRate.iou())}, {JS(changes), value_from(output.bookChanges)},
{JS(open), to_string(change.openRate.iou())},
{JS(close), to_string(change.closeRate.iou())},
}; };
} }
json::object const BookChangesHandler::Input
tag_invoke(boost::json::value_to_tag<BookChangesHandler::Input>, 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<Backend::TransactionAndMetadata> const& transactions) computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector<Backend::TransactionAndMetadata> const& transactions)
{ {
using boost::json::value_from;
return { return {
{JS(type), "bookChanges"}, {JS(type), "bookChanges"},
{JS(ledger_index), lgrInfo.seq}, {JS(ledger_index), lgrInfo.seq},
{JS(ledger_hash), to_string(lgrInfo.hash)}, {JS(ledger_hash), to_string(lgrInfo.hash)},
{JS(ledger_time), lgrInfo.closeTime.time_since_epoch().count()}, {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<Status>(&info))
return *status;
auto const lgrInfo = std::get<ripple::LedgerInfo>(info);
auto const transactions = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq, context.yield);
return computeBookChanges(lgrInfo, transactions);
}
} // namespace RPC } // namespace RPC

View File

@@ -25,7 +25,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class BookChangesHandler class BookChangesHandler
{ {
@@ -48,7 +48,7 @@ public:
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
BookChangesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) BookChangesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -61,6 +61,7 @@ public:
{JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator}, {JS(ledger_index), validation::LedgerIndexValidator},
}; };
return rpcSpec; return rpcSpec;
} }
@@ -74,4 +75,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,90 +17,85 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/handlers/BookOffers.h>
#include <boost/json.hpp>
#include <algorithm>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
Result BookOffersHandler::Result
doBookOffers(Context const& context) 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<Status>(&bookMaybe))
return Error{*status};
boost::json::object response = {}; // check ledger
auto v = ledgerInfoFromRequest(context); auto const range = sharedPtrBackend_->fetchLedgerRange();
if (auto status = std::get_if<Status>(&v)) auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
return *status; *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto lgrInfo = std::get<ripple::LedgerInfo>(v); if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
ripple::Book book; auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
ripple::uint256 bookBase; auto const book = std::get<ripple::Book>(bookMaybe);
if (request.contains("book")) 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<BookOffersHandler::Input>, 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()) if (!jsonObject.at(JS(ledger_index)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "bookNotString"}; input.ledgerIndex = jv.at(JS(ledger_index)).as_int64();
else if (jsonObject.at(JS(ledger_index)).as_string() != "validated")
if (!bookBase.parseHex(request.at("book").as_string().c_str())) input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str());
return Status{RippledError::rpcINVALID_PARAMS, "invalidBook"};
}
else
{
auto parsed = parseBook(request);
if (auto status = std::get_if<Status>(&parsed))
return *status;
else
{
book = std::get<ripple::Book>(parsed);
bookBase = getBookBase(book);
}
} }
std::uint32_t limit; if (jsonObject.contains(JS(taker)))
if (auto const status = getLimit(context, limit); status) input.taker = accountFromStringStrict(jv.at(JS(taker)).as_string().c_str());
return status;
ripple::AccountID takerID = beast::zero; if (jsonObject.contains(JS(limit)))
if (auto const status = getTaker(request, takerID); status) input.limit = jv.at(JS(limit)).as_int64();
return status;
auto start = std::chrono::system_clock::now(); return input;
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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(end2 - end).count()
<< " milliseconds - request = " << request;
return response;
} }
} // namespace RPC } // namespace RPC

View File

@@ -23,7 +23,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class BookOffersHandler class BookOffersHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -37,8 +37,8 @@ public:
bool validated = true; bool validated = true;
}; };
// the taker is not really used in both clio and rippled, both of them // the taker is not really used in both clio and rippled, both of them return all the offers regardless the funding
// return all the offers regardless the funding status // status
struct Input struct Input
{ {
std::optional<std::string> ledgerHash; std::optional<std::string> ledgerHash;
@@ -47,13 +47,12 @@ public:
std::optional<ripple::AccountID> taker; std::optional<ripple::AccountID> taker;
ripple::Currency paysCurrency; ripple::Currency paysCurrency;
ripple::Currency getsCurrency; ripple::Currency getsCurrency;
// accountID will be filled by input converter, if no issuer is given, // accountID will be filled by input converter, if no issuer is given, will use XRP issuer
// will use XRP issuer
ripple::AccountID paysID = ripple::xrpAccount(); ripple::AccountID paysID = ripple::xrpAccount();
ripple::AccountID getsID = ripple::xrpAccount(); ripple::AccountID getsID = ripple::xrpAccount();
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
BookOffersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) BookOffersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -70,10 +69,10 @@ public:
{JS(currency), {JS(currency),
validation::Required{}, validation::Required{},
validation::WithCustomError{ validation::WithCustomError{
validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcDST_AMT_MALFORMED)}}, validation::CurrencyValidator, Status(RippledError::rpcDST_AMT_MALFORMED)}},
{JS(issuer), {JS(issuer),
validation::WithCustomError{ validation::WithCustomError{
validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcDST_ISR_MALFORMED)}}}}, validation::IssuerValidator, Status(RippledError::rpcDST_ISR_MALFORMED)}}}},
{JS(taker_pays), {JS(taker_pays),
validation::Required{}, validation::Required{},
validation::Type<boost::json::object>{}, validation::Type<boost::json::object>{},
@@ -81,10 +80,10 @@ public:
{JS(currency), {JS(currency),
validation::Required{}, validation::Required{},
validation::WithCustomError{ validation::WithCustomError{
validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcSRC_CUR_MALFORMED)}}, validation::CurrencyValidator, Status(RippledError::rpcSRC_CUR_MALFORMED)}},
{JS(issuer), {JS(issuer),
validation::WithCustomError{ validation::WithCustomError{
validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcSRC_ISR_MALFORMED)}}}}, validation::IssuerValidator, Status(RippledError::rpcSRC_ISR_MALFORMED)}}}},
{JS(taker), validation::AccountValidator}, {JS(taker), validation::AccountValidator},
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}}, {JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}},
{JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_hash), validation::Uint256HexStringValidator},
@@ -104,4 +103,4 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -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 <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/PayChan.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/jss.h>
#include <ripple/resource/Fees.h>
#include <optional>
#include <rpc/RPCHelpers.h>
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<Status>(&v))
return *status;
auto const [pk, sk] = std::get<std::pair<ripple::PublicKey, ripple::SecretKey>>(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

View File

@@ -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 <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/PayChan.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/jss.h>
#include <ripple/resource/Fees.h>
#include <optional>
#include <rpc/RPCHelpers.h>
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<ripple::PublicKey> pk;
{
std::string const strPk = request.at(JS(public_key)).as_string().c_str();
pk = ripple::parseBase58<ripple::PublicKey>(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

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,177 +17,138 @@
*/ */
//============================================================================== //==============================================================================
#include <backend/BackendInterface.h> #include <rpc/handlers/GatewayBalances.h>
#include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
Result GatewayBalancesHandler::Result
doGatewayBalances(Context const& context) GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context const& ctx) const
{ {
auto request = context.params; // check ledger
boost::json::object response = {}; 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 = std::get_if<Status>(&lgrInfoOrStatus))
if (auto const status = getAccount(request, accountID); status) return Error{*status};
return status;
auto v = ledgerInfoFromRequest(context); // check account
if (auto status = std::get_if<Status>(&v)) auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
return *status; auto const accountID = accountFromStringStrict(input.account);
auto const accountLedgerObject =
sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield);
auto lgrInfo = std::get<ripple::LedgerInfo>(v); if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
std::map<ripple::Currency, ripple::STAmount> sums; auto output = GatewayBalancesHandler::Output{};
std::map<ripple::AccountID, std::vector<ripple::STAmount>> hotBalances;
std::map<ripple::AccountID, std::vector<ripple::STAmount>> assets;
std::map<ripple::AccountID, std::vector<ripple::STAmount>> frozenBalances;
std::set<ripple::AccountID> hotWallets;
if (request.contains(JS(hotwallet)))
{
auto getAccountID = [](auto const& j) -> std::optional<ripple::AccountID> {
if (j.is_string())
{
auto const pk =
ripple::parseBase58<ripple::PublicKey>(ripple::TokenType::AccountPublic, j.as_string().c_str());
if (pk)
{
return ripple::calcAccountID(*pk);
}
return ripple::parseBase58<ripple::AccountID>(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) { auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE) if (sle.getType() == ripple::ltRIPPLE_STATE)
{ {
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); 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) if (!viewLowest)
balance.negate(); balance.negate();
int balSign = balance.signum(); auto const balSign = balance.signum();
if (balSign == 0) if (balSign == 0)
return true; return true;
auto const& peer = !viewLowest ? lowID : highID; auto const& peer = !viewLowest ? lowID : highID;
// Here, a negative balance means the cold wallet owes (normal) // Here, a negative balance means the cold wallet owes (normal)
// A positive balance means the cold wallet has an asset // A positive balance means the cold wallet has an asset (unusual)
// (unusual)
if (hotWallets.count(peer) > 0) if (input.hotWallets.count(peer) > 0)
{ {
// This is a specified hot wallet // This is a specified hot wallet
hotBalances[peer].push_back(-balance); output.hotBalances[peer].push_back(-balance);
} }
else if (balSign > 0) else if (balSign > 0)
{ {
// This is a gateway asset // This is a gateway asset
assets[peer].push_back(balance); output.assets[peer].push_back(balance);
} }
else if (freeze) else if (freeze)
{ {
// An obligation the gateway has frozen // An obligation the gateway has frozen
frozenBalances[peer].push_back(-balance); output.frozenBalances[peer].push_back(-balance);
} }
else else
{ {
// normal negative balance, obligation to customer // normal negative balance, obligation to customer
auto& bal = sums[balance.getCurrency()]; auto& bal = output.sums[balance.getCurrency()];
if (bal == beast::zero) if (bal == beast::zero)
{ {
// This is needed to set the currency code correctly // This is needed to set the currency code correctly
bal = -balance; bal = -balance;
} }
else else
{ // when overflow happens, insert a flag to indicate {
// https://github.com/XRPLF/rippled/pull/4355
try try
{ {
bal -= balance; bal -= balance;
} }
catch (std::runtime_error& e) catch (std::runtime_error const& e)
{ {
response["overflow"] = true; output.overflow = true;
} }
} }
} }
} }
return true; return true;
}; };
auto result = traverseOwnedNodes( // traverse all owned nodes, limit->max, marker->empty
*context.backend, auto const ret = ngTraverseOwnedNodes(
accountID, *sharedPtrBackend_,
*accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
context.yield, ctx.yield,
addToResponse); addToResponse);
if (auto status = std::get_if<RPC::Status>(&result))
return *status;
if (!sums.empty()) if (auto status = std::get_if<Status>(&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; boost::json::object obligations;
for (auto const& [k, v] : sums) for (auto const& [k, v] : output.sums)
{ obligations[ripple::to_string(k)] = v.getText();
obj[ripple::to_string(k)] = v.getText();
} obj[JS(obligations)] = std::move(obligations);
response[JS(obligations)] = std::move(obj);
} }
auto toJson = [](std::map<ripple::AccountID, std::vector<ripple::STAmount>> const& balances) { auto const toJson = [](std::map<ripple::AccountID, std::vector<ripple::STAmount>> const& balances) {
boost::json::object obj; boost::json::object balancesObj;
if (!balances.empty())
if (not balances.empty())
{ {
for (auto const& [accId, accBalances] : balances) for (auto const& [accId, accBalances] : balances)
{ {
@@ -199,25 +160,72 @@ doGatewayBalances(Context const& context)
entry[JS(value)] = balance.getText(); entry[JS(value)] = balance.getText();
arr.push_back(std::move(entry)); 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 (auto balances = toJson(output.hotBalances); balances.size())
if (not std::all_of(hotWallets.begin(), hotWallets.end(), containsHotWallet)) obj[JS(balances)] = balances;
return Status{RippledError::rpcINVALID_PARAMS, "invalidHotWallet"};
if (auto balances = toJson(hotBalances); balances.size()) // we don't have frozen_balances field in the
response[JS(balances)] = balances; // document:https://xrpl.org/gateway_balances.html#gateway_balances
if (auto balances = toJson(frozenBalances); balances.size()) if (auto balances = toJson(output.frozenBalances); balances.size())
response[JS(frozen_balances)] = balances; obj[JS(frozen_balances)] = balances;
if (auto balances = toJson(assets); assets.size())
response[JS(assets)] = toJson(assets); if (auto balances = toJson(output.assets); balances.size())
response[JS(account)] = request.at(JS(account)); obj[JS(assets)] = balances;
response[JS(ledger_index)] = lgrInfo.seq;
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); obj[JS(account)] = output.accountID;
return response; 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<GatewayBalancesHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class GatewayBalancesHandler class GatewayBalancesHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -53,7 +53,7 @@ public:
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
GatewayBalancesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) GatewayBalancesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -66,10 +66,8 @@ public:
static auto const hotWalletValidator = static auto const hotWalletValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string() && !value.is_array()) if (!value.is_string() && !value.is_array())
{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}};
return Error{
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}};
}
// wallet needs to be an valid accountID or public key // 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 wallets = value.is_array() ? value.as_array() : boost::json::array{value};
auto const getAccountID = [](auto const& j) -> std::optional<ripple::AccountID> { auto const getAccountID = [](auto const& j) -> std::optional<ripple::AccountID> {
@@ -77,17 +75,22 @@ public:
{ {
auto const pk = ripple::parseBase58<ripple::PublicKey>( auto const pk = ripple::parseBase58<ripple::PublicKey>(
ripple::TokenType::AccountPublic, j.as_string().c_str()); ripple::TokenType::AccountPublic, j.as_string().c_str());
if (pk) if (pk)
return ripple::calcAccountID(*pk); return ripple::calcAccountID(*pk);
return ripple::parseBase58<ripple::AccountID>(j.as_string().c_str()); return ripple::parseBase58<ripple::AccountID>(j.as_string().c_str());
} }
return {}; return {};
}; };
for (auto const& wallet : wallets) for (auto const& wallet : wallets)
{ {
if (!getAccountID(wallet)) 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{}; return MaybeError{};
}}; }};
@@ -111,4 +114,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,160 +17,166 @@
*/ */
//============================================================================== //==============================================================================
#include <backend/BackendInterface.h> #include <rpc/handlers/Ledger.h>
#include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
LedgerHandler::Result
Result LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const
doLedger(Context const& context)
{ {
auto params = context.params; auto const range = sharedPtrBackend_->fetchLedgerRange();
boost::json::object response = {}; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
bool binary = false; if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
if (params.contains(JS(binary))) return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
Output output;
if (input.binary)
{ {
if (!params.at(JS(binary)).is_bool()) output.header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
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<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
boost::json::object header;
if (binary)
{
header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
} }
else else
{ {
header[JS(accepted)] = true; output.header[JS(accepted)] = true;
header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); output.header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
header[JS(close_flags)] = lgrInfo.closeFlags; output.header[JS(close_flags)] = lgrInfo.closeFlags;
header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); output.header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count();
header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); output.header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); output.header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count();
header[JS(closed)] = true; output.header[JS(closed)] = true;
header[JS(hash)] = ripple::strHex(lgrInfo.hash); output.header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); output.header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_index)] = std::to_string(lgrInfo.seq); output.header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); output.header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count();
header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); output.header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash);
header[JS(seqNum)] = std::to_string(lgrInfo.seq); output.header[JS(seqNum)] = std::to_string(lgrInfo.seq);
header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); output.header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops);
header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); output.header[JS(total_coins)] = ripple::to_string(lgrInfo.drops);
header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); 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); output.header[JS(transactions)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonTxs = header.at(JS(transactions)).as_array(); boost::json::array& jsonTxs = output.header.at(JS(transactions)).as_array();
if (expand)
if (input.expand)
{ {
auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq, context.yield); auto txns = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield);
std::transform( std::transform(
std::move_iterator(txns.begin()), std::move_iterator(txns.begin()),
std::move_iterator(txns.end()), std::move_iterator(txns.end()),
std::back_inserter(jsonTxs), std::back_inserter(jsonTxs),
[binary](auto obj) { [&](auto obj) {
boost::json::object entry; boost::json::object entry;
if (!binary) if (!input.binary)
{ {
auto [txn, meta] = toExpandedJson(obj); auto [txn, meta] = toExpandedJson(obj);
entry = txn; entry = std::move(txn);
entry[JS(metaData)] = meta; entry[JS(metaData)] = std::move(meta);
} }
else else
{ {
entry[JS(tx_blob)] = ripple::strHex(obj.transaction); entry[JS(tx_blob)] = ripple::strHex(obj.transaction);
entry[JS(meta)] = ripple::strHex(obj.metadata); entry[JS(meta)] = ripple::strHex(obj.metadata);
} }
// entry[JS(ledger_index)] = obj.ledgerSequence;
return entry; return entry;
}); });
} }
else else
{ {
auto hashes = context.backend->fetchAllTransactionHashesInLedger(lgrInfo.seq, context.yield); auto hashes = sharedPtrBackend_->fetchAllTransactionHashesInLedger(lgrInfo.seq, ctx.yield);
std::transform( std::transform(
std::move_iterator(hashes.begin()), std::move_iterator(hashes.begin()),
std::move_iterator(hashes.end()), std::move_iterator(hashes.end()),
std::back_inserter(jsonTxs), std::back_inserter(jsonTxs),
[](auto hash) { [](auto hash) { return boost::json::string(ripple::strHex(hash)); });
boost::json::object entry;
return boost::json::string(ripple::strHex(hash));
});
} }
} }
if (diff) if (input.diff)
{ {
header["diff"] = boost::json::value(boost::json::array_kind); output.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); boost::json::array& jsonDiff = output.header.at("diff").as_array();
auto diff = sharedPtrBackend_->fetchLedgerDiff(lgrInfo.seq, ctx.yield);
for (auto const& obj : diff) for (auto const& obj : diff)
{ {
boost::json::object entry; boost::json::object entry;
entry["object_id"] = ripple::strHex(obj.key); entry["object_id"] = ripple::strHex(obj.key);
if (binary)
if (input.binary)
{
entry["object"] = ripple::strHex(obj.blob); entry["object"] = ripple::strHex(obj.blob);
}
else if (obj.blob.size()) 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); entry["object"] = toJson(sle);
} }
else else
{
entry["object"] = ""; entry["object"] = "";
}
jsonDiff.push_back(std::move(entry)); jsonDiff.push_back(std::move(entry));
} }
} }
response[JS(ledger)] = header; output.ledgerHash = ripple::strHex(lgrInfo.hash);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); output.ledgerIndex = lgrInfo.seq;
response[JS(ledger_index)] = lgrInfo.seq;
return response; 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<LedgerHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class LedgerHandler class LedgerHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -52,7 +52,7 @@ public:
bool diff = false; bool diff = false;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
LedgerHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) LedgerHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -86,4 +86,4 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,98 +17,40 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/handlers/LedgerData.h>
#include <ripple/app/ledger/LedgerToJson.h> #include <ripple/app/ledger/LedgerToJson.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <backend/BackendInterface.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp> #include <algorithm>
// 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
namespace RPC { namespace RPC {
using boost::json::value_to; LedgerDataHandler::Result
LedgerDataHandler::process(Input input, Context const& ctx) const
Result
doLedgerData(Context const& context)
{ {
auto request = context.params; // marker must be int if outOfOrder is true
boost::json::object response = {}; 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; auto const range = sharedPtrBackend_->fetchLedgerRange();
if (auto const status = getLimit(context, limit); status) auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
return status; *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (!binary) if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
limit = std::clamp(limit, {1}, {256}); return Error{*status};
bool outOfOrder = false; auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
if (request.contains("out_of_order"))
// 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()) if (input.binary)
return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
outOfOrder = request.at("out_of_order").as_bool();
}
std::optional<ripple::uint256> marker;
std::optional<uint32_t> 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<uint32_t>(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<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(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)
{ {
header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
} }
@@ -132,79 +74,155 @@ doLedgerData(Context const& context)
} }
header[JS(closed)] = true; header[JS(closed)] = true;
response[JS(ledger)] = header; output.header = std::move(header);
} }
else else
{ {
if (!outOfOrder && !context.backend->fetchLedgerObject(*marker, lgrInfo.seq, context.yield)) if (input.marker && !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield))
return Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}; return Error{Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}};
} }
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); output.ledgerHash = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq; output.ledgerIndex = lgrInfo.seq;
auto start = std::chrono::system_clock::now(); auto const start = std::chrono::system_clock::now();
std::vector<Backend::LedgerObject> results; std::vector<Backend::LedgerObject> results;
if (diffMarker)
if (input.diffMarker)
{ {
assert(outOfOrder); // keep the same logic as previous implementation
auto diff = context.backend->fetchLedgerDiff(*diffMarker, context.yield); auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield);
std::vector<ripple::uint256> keys; std::vector<ripple::uint256> keys;
for (auto&& [key, object] : diff)
for (auto& [key, object] : diff)
{ {
if (!object.size()) if (!object.size())
{
keys.push_back(std::move(key)); 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) for (size_t i = 0; i < objs.size(); ++i)
{ {
auto&& obj = objs[i]; auto& obj = objs[i];
if (obj.size()) if (obj.size())
results.push_back({std::move(keys[i]), std::move(obj)}); 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 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); results = std::move(page.objects);
if (page.cursor) if (page.cursor)
response["marker"] = ripple::strHex(*(page.cursor)); output.marker = ripple::strHex(*(page.cursor));
else if (outOfOrder) else if (input.outOfOrder)
response["marker"] = context.backend->fetchLedgerRange()->maxSequence; output.diffMarker = sharedPtrBackend_->fetchLedgerRange()->maxSequence;
} }
auto end = std::chrono::system_clock::now();
auto time = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); auto const end = std::chrono::system_clock::now();
log_.debug() << "Number of results = " << results.size() << " fetched in "
<< std::chrono::duration_cast<std::chrono::microseconds>(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) for (auto const& [key, object] : results)
{ {
ripple::STLedgerEntry sle{ripple::SerialIter{object.data(), object.size()}, key}; ripple::STLedgerEntry const sle{ripple::SerialIter{object.data(), object.size()}, key};
if (binary)
if (input.binary)
{ {
boost::json::object entry; boost::json::object entry;
entry[JS(data)] = ripple::serializeHex(sle); entry[JS(data)] = ripple::serializeHex(sle);
entry[JS(index)] = ripple::to_string(sle.key()); entry[JS(index)] = ripple::to_string(sle.key());
objects.push_back(std::move(entry)); output.states.push_back(std::move(entry));
} }
else 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<std::chrono::microseconds>(end2 - end).count(); if (input.outOfOrder)
gLog.debug() << "Number of results = " << results.size() << " serialized in " << time << " microseconds"; 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<std::chrono::microseconds>(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<LedgerDataHandler::Input>, 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 } // namespace RPC

View File

@@ -24,12 +24,17 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class LedgerDataHandler class LedgerDataHandler
{ {
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
clio::Logger log_{"RPC"}; clio::Logger log_{"RPC"};
// constants
static uint32_t constexpr LIMITBINARY = 2048;
static uint32_t constexpr LIMITJSON = 256;
public: public:
struct Output struct Output
{ {
@@ -57,7 +62,7 @@ public:
bool outOfOrder = false; bool outOfOrder = false;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
LedgerDataHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) LedgerDataHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -83,13 +88,10 @@ public:
process(Input input, Context const& ctx) const; process(Input input, Context const& ctx) const;
private: private:
static uint32_t constexpr LIMITBINARY = 2048;
static uint32_t constexpr LIMITJSON = 256;
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,359 +17,223 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/protocol/Indexes.h> #include <rpc/handlers/LedgerEntry.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <boost/json.hpp>
#include <backend/BackendInterface.h> #include <unordered_map>
#include <rpc/RPCHelpers.h>
// {
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// ...
// }
namespace RPC { namespace RPC {
using boost::json::value_to; LedgerEntryHandler::Result
LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) const
Result
doLedgerEntry(Context const& context)
{ {
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<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::uint256 key; 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 if (input.index)
// time. see https://xrpl.org/ledger_entry.html#ledger_entry
if (request.contains(JS(index)))
{ {
if (!request.at(JS(index)).is_string()) key = ripple::uint256{std::string_view(*(input.index))};
return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"};
if (!key.parseHex(request.at(JS(index)).as_string().c_str()))
return Status{ClioError::rpcMALFORMED_REQUEST};
} }
else if (request.contains(JS(account_root))) else if (input.accountRoot)
{ {
if (!request.at(JS(account_root)).is_string()) key = ripple::keylet::account(*ripple::parseBase58<ripple::AccountID>(*(input.accountRoot))).key;
return Status{RippledError::rpcINVALID_PARAMS, "account_rootNotString"};
auto const account = ripple::parseBase58<ripple::AccountID>(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;
} }
else if (request.contains(JS(check))) else if (input.directory)
{ {
if (!request.at(JS(check)).is_string()) auto const keyOrStatus = composeKeyFromDirectory(*input.directory);
return Status{RippledError::rpcINVALID_PARAMS, "checkNotString"}; if (auto const status = std::get_if<Status>(&keyOrStatus))
return Error{*status};
expectedType = ripple::ltCHECK; key = std::get<ripple::uint256>(keyOrStatus);
if (!key.parseHex(request.at(JS(check)).as_string().c_str()))
{
return Status{RippledError::rpcINVALID_PARAMS, "checkMalformed"};
}
} }
else if (request.contains(JS(deposit_preauth))) else if (input.offer)
{ {
expectedType = ripple::ltDEPOSIT_PREAUTH; auto const id = ripple::parseBase58<ripple::AccountID>(input.offer->at(JS(account)).as_string().c_str());
if (!request.at(JS(deposit_preauth)).is_object()) key = ripple::keylet::offer(*id, boost::json::value_to<std::uint32_t>(input.offer->at(JS(seq)))).key;
{
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<ripple::AccountID>(deposit_preauth.at(JS(owner)).as_string().c_str());
auto const authorized =
ripple::parseBase58<ripple::AccountID>(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;
}
} }
else if (request.contains(JS(directory))) else if (input.rippleStateAccount)
{ {
expectedType = ripple::ltDIR_NODE; auto const id1 = ripple::parseBase58<ripple::AccountID>(
if (!request.at(JS(directory)).is_object()) input.rippleStateAccount->at(JS(accounts)).as_array().at(0).as_string().c_str());
{ auto const id2 = ripple::parseBase58<ripple::AccountID>(
if (!request.at(JS(directory)).is_string()) input.rippleStateAccount->at(JS(accounts)).as_array().at(1).as_string().c_str());
return Status{RippledError::rpcINVALID_PARAMS, "directoryNotString"}; auto const currency = ripple::to_currency(input.rippleStateAccount->at(JS(currency)).as_string().c_str());
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<std::uint64_t>(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<ripple::AccountID>(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<ripple::AccountID>(
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<ripple::AccountID>(offer.at(JS(account)).as_string().c_str());
if (!id)
return Status{ClioError::rpcMALFORMED_ADDRESS};
else
{
std::uint32_t seq = boost::json::value_to<std::uint32_t>(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<ripple::AccountID>(state.at(JS(accounts)).as_array().at(0).as_string().c_str());
auto const id2 =
ripple::parseBase58<ripple::AccountID>(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"};
key = ripple::keylet::line(*id1, *id2, currency).key; key = ripple::keylet::line(*id1, *id2, currency).key;
} }
else if (request.contains(JS(ticket))) else if (input.escrow)
{ {
expectedType = ripple::ltTICKET; auto const id = ripple::parseBase58<ripple::AccountID>(input.escrow->at(JS(owner)).as_string().c_str());
// ticket object : account, ticket_seq key = ripple::keylet::escrow(*id, input.escrow->at(JS(seq)).as_int64()).key;
if (!request.at(JS(ticket)).is_object()) }
{ else if (input.depositPreauth)
if (!request.at(JS(ticket)).is_string()) {
return Status{ClioError::rpcMALFORMED_REQUEST, "ticketNotString"}; auto const owner =
ripple::parseBase58<ripple::AccountID>(input.depositPreauth->at(JS(owner)).as_string().c_str());
auto const authorized =
ripple::parseBase58<ripple::AccountID>(input.depositPreauth->at(JS(authorized)).as_string().c_str());
if (!key.parseHex(request.at(JS(ticket)).as_string().c_str())) key = ripple::keylet::depositPreauth(*owner, *authorized).key;
return Status{ClioError::rpcMALFORMED_REQUEST, "malformedTicket"}; }
} else if (input.ticket)
else if ( {
!request.at(JS(ticket)).as_object().contains(JS(account)) || auto const id = ripple::parseBase58<ripple::AccountID>(input.ticket->at(JS(account)).as_string().c_str());
!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<ripple::AccountID>(
request.at(JS(ticket)).as_object().at(JS(account)).as_string().c_str());
if (!id) key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64());
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);
}
}
} }
else 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) if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Status{"entryNotFound"}; return Error{*status};
// check expected type matches actual type auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
ripple::STLedgerEntry sle{ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key}; auto const ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield);
if (expectedType != ripple::ltANY && sle.getType() != expectedType)
return Status{"unexpectedLedgerType"};
response[JS(index)] = ripple::strHex(key); if (!ledgerObject || ledgerObject->size() == 0)
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); return Error{Status{"entryNotFound"}};
response[JS(ledger_index)] = lgrInfo.seq;
if (binary) ripple::STLedgerEntry const sle{ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key};
{
response[JS(node_binary)] = ripple::strHex(*dbResponse); 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 else
output.node = toJson(sle);
return output;
}
std::variant<ripple::uint256, Status>
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<uint64_t>(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<ripple::AccountID>(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<LedgerEntryHandler::Input>, 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<std::string, ripple::LedgerEntryType>{
{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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class LedgerEntryHandler class LedgerEntryHandler
{ {
@@ -63,7 +63,7 @@ public:
std::optional<boost::json::object> ticket; std::optional<boost::json::object> ticket;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
LedgerEntryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) LedgerEntryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -80,11 +80,16 @@ public:
if (!value.is_array() || value.as_array().size() != 2 || !value.as_array()[0].is_string() || if (!value.is_array() || value.as_array().size() != 2 || !value.as_array()[0].is_string() ||
!value.as_array()[1].is_string() || !value.as_array()[1].is_string() ||
value.as_array()[0].as_string() == value.as_array()[1].as_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<ripple::AccountID>(value.as_array()[0].as_string().c_str()); auto const id1 = ripple::parseBase58<ripple::AccountID>(value.as_array()[0].as_string().c_str());
auto const id2 = ripple::parseBase58<ripple::AccountID>(value.as_array()[1].as_string().c_str()); auto const id2 = ripple::parseBase58<ripple::AccountID>(value.as_array()[1].as_string().c_str());
if (!id1 || !id2) if (!id1 || !id2)
return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}}; return Error{Status{ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}};
return MaybeError{}; return MaybeError{};
}}; }};
@@ -156,7 +161,7 @@ public:
private: private:
// dir_root and owner can not be both empty or filled at the same time // 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 // This function will return an error if this is the case
std::variant<ripple::uint256, RPC::Status> std::variant<ripple::uint256, Status>
composeKeyFromDirectory(boost::json::object const& directory) const noexcept; composeKeyFromDirectory(boost::json::object const& directory) const noexcept;
friend void friend void
@@ -165,4 +170,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,28 +17,29 @@
*/ */
//============================================================================== //==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/handlers/LedgerRange.h>
#include <optional>
namespace RPC { namespace RPC {
Result LedgerRangeHandler::Result
doLedgerRange(Context const& context) LedgerRangeHandler::process() const
{ {
boost::json::object response = {}; if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange)
return Output{*maybeRange};
auto range = context.backend->fetchLedgerRange();
if (!range)
{
return Status{RippledError::rpcNOT_READY, "rangeNotFound"};
}
else else
{ return Error{Status{RippledError::rpcNOT_READY, "rangeNotFound"}};
response[JS(ledger_index_min)] = range->minSequence; }
response[JS(ledger_index_max)] = range->maxSequence;
}
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 } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <memory> #include <memory>
namespace RPCng { namespace RPC {
class LedgerRangeHandler class LedgerRangeHandler
{ {
@@ -51,4 +51,4 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -18,20 +18,21 @@
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/NFTBuyOffers.h> #include <rpc/handlers/NFTBuyOffers.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
using namespace ripple; using namespace ripple;
namespace RPCng { namespace RPC {
NFTBuyOffersHandler::Result NFTBuyOffersHandler::Result
NFTBuyOffersHandler::process(NFTBuyOffersHandler::Input input, Context const& ctx) const NFTBuyOffersHandler::process(NFTBuyOffersHandler::Input input, Context const& ctx) const
{ {
auto const tokenID = uint256{input.nftID.c_str()}; auto const tokenID = uint256{input.nftID.c_str()};
auto const directory = keylet::nft_buys(tokenID); auto const directory = keylet::nft_buys(tokenID);
return iterateOfferDirectory(input, tokenID, directory, ctx.yield); return iterateOfferDirectory(input, tokenID, directory, ctx.yield);
} }
} // namespace RPCng } // namespace RPC

View File

@@ -20,9 +20,9 @@
#pragma once #pragma once
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/ngHandlers/NFTOffersCommon.h> #include <rpc/handlers/NFTOffersCommon.h>
namespace RPCng { namespace RPC {
class NFTBuyOffersHandler : public NFTOffersHandlerBase class NFTBuyOffersHandler : public NFTOffersHandlerBase
{ {
public: public:
@@ -34,4 +34,4 @@ public:
Result Result
process(Input input, Context const& ctx) const; process(Input input, Context const& ctx) const;
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,50 +17,196 @@
*/ */
//============================================================================== //==============================================================================
#include <log/Logger.h> #include <rpc/handlers/NFTHistory.h>
#include <rpc/RPCHelpers.h>
#include <util/Profiler.h> #include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
Result // TODO: this is currently very similar to account_tx but its own copy for time
doNFTHistory(Context const& context) // 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); auto const range = sharedPtrBackend_->fetchLedgerRange();
if (auto const status = std::get_if<Status>(&maybeTokenID); status) auto [minIndex, maxIndex] = *range;
return *status;
auto const tokenID = std::get<ripple::uint256>(maybeTokenID);
constexpr std::string_view outerFuncName = __func__; if (input.ledgerIndexMin)
auto const maybeResponse = traverseTransactions( {
context, if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin)
[&tokenID, &outerFuncName]( return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
std::shared_ptr<Backend::BackendInterface const> const& backend,
std::uint32_t const limit,
bool const forward,
std::optional<Backend::TransactionsCursor> 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 (auto const status = std::get_if<Status>(&maybeResponse); status) minIndex = *input.ledgerIndexMin;
return *status; }
auto response = std::get<boost::json::object>(maybeResponse);
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<Status>(&lgrInfoOrStatus))
return Error{*status};
maxIndex = minIndex = std::get<ripple::LedgerInfo>(lgrInfoOrStatus).seq;
}
std::optional<Backend::TransactionsCursor> 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; 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<NFTHistoryHandler::Input>, 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 } // namespace RPC

View File

@@ -25,7 +25,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class NFTHistoryHandler class NFTHistoryHandler
{ {
clio::Logger log_{"RPC"}; clio::Logger log_{"RPC"};
@@ -52,7 +52,7 @@ public:
bool validated = true; bool validated = true;
}; };
// TODO: we did not implement the "strict" field // TODO: We did not implement the "strict" field
struct Input struct Input
{ {
std::string nftID; std::string nftID;
@@ -68,7 +68,7 @@ public:
std::optional<Marker> marker; std::optional<Marker> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
NFTHistoryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) NFTHistoryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -88,8 +88,7 @@ public:
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}}, {JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}},
{JS(marker), {JS(marker),
validation::WithCustomError{ validation::WithCustomError{
validation::Type<boost::json::object>{}, validation::Type<boost::json::object>{}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}},
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "invalidMarker"}},
validation::Section{ validation::Section{
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}}, {JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}}, {JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
@@ -112,4 +111,5 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,48 +17,96 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/NFTInfo.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <boost/json.hpp>
#include <backend/BackendInterface.h> using namespace ripple;
#include <rpc/RPCHelpers.h> using namespace ::RPC;
namespace RPC { namespace RPC {
Result NFTInfoHandler::Result
doNFTInfo(Context const& context) NFTInfoHandler::process(NFTInfoHandler::Input input, Context const& ctx) const
{ {
auto const request = context.params; auto const tokenID = ripple::uint256{input.nftID.c_str()};
boost::json::object response = {}; 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<Status>(&lgrInfoOrStatus))
if (auto const status = std::get_if<Status>(&maybeTokenID); status) return Error{*status};
return *status;
auto const tokenID = std::get<ripple::uint256>(maybeTokenID);
auto const maybeLedgerInfo = ledgerInfoFromRequest(context); auto const lgrInfo = std::get<LedgerInfo>(lgrInfoOrStatus);
if (auto const status = std::get_if<Status>(&maybeLedgerInfo); status) auto const maybeNft = sharedPtrBackend_->fetchNFT(tokenID, lgrInfo.seq, ctx.yield);
return *status;
auto const lgrInfo = std::get<ripple::LedgerInfo>(maybeLedgerInfo);
auto const dbResponse = context.backend->fetchNFT(tokenID, lgrInfo.seq, context.yield); if (not maybeNft.has_value())
if (!dbResponse) return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"}};
return Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"};
response[JS(nft_id)] = ripple::strHex(dbResponse->tokenID); auto const& nft = *maybeNft;
response[JS(ledger_index)] = dbResponse->ledgerSequence; auto output = NFTInfoHandler::Output{};
response[JS(owner)] = ripple::toBase58(dbResponse->owner);
response["is_burned"] = dbResponse->isBurned;
response[JS(uri)] = ripple::strHex(dbResponse->uri);
response[JS(flags)] = ripple::nft::getFlags(dbResponse->tokenID); output.nftID = strHex(nft.tokenID);
response["transfer_fee"] = ripple::nft::getTransferFee(dbResponse->tokenID); output.ledgerIndex = nft.ledgerSequence;
response[JS(issuer)] = ripple::toBase58(ripple::nft::getIssuer(dbResponse->tokenID)); output.owner = toBase58(nft.owner);
response["nft_taxon"] = ripple::nft::toUInt32(ripple::nft::getTaxon(dbResponse->tokenID)); output.isBurned = nft.isBurned;
response[JS(nft_serial)] = ripple::nft::getSerial(dbResponse->tokenID); 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<NFTInfoHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class NFTInfoHandler class NFTInfoHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -56,7 +56,7 @@ public:
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
NFTInfoHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) NFTInfoHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -84,4 +84,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -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 <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <algorithm>
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<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(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<ripple::SLE> 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<RPC::Status>(&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<Status>(&v))
return *status;
auto const getKeylet = [sells, &v]() {
if (sells)
return ripple::keylet::nft_sells(std::get<ripple::uint256>(v));
return ripple::keylet::nft_buys(std::get<ripple::uint256>(v));
};
return enumerateNFTOffers(context, std::get<ripple::uint256>(v), getKeylet());
}
Result
doNFTSellOffers(Context const& context)
{
return doNFTOffers(context, true);
}
Result
doNFTBuyOffers(Context const& context)
{
return doNFTOffers(context, false);
}
} // namespace RPC

View File

@@ -18,7 +18,7 @@
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/NFTOffersCommon.h> #include <rpc/handlers/NFTOffersCommon.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
@@ -32,7 +32,7 @@ namespace ripple {
inline void inline void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, SLE const& offer) 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 = { boost::json::object obj = {
{JS(nft_offer_index), to_string(offer.key())}, {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 ripple
namespace RPCng { namespace RPC {
NFTOffersHandlerBase::Result NFTOffersHandlerBase::Result
NFTOffersHandlerBase::iterateOfferDirectory( NFTOffersHandlerBase::iterateOfferDirectory(
@@ -64,6 +64,7 @@ NFTOffersHandlerBase::iterateOfferDirectory(
auto const range = sharedPtrBackend_->fetchLedgerRange(); auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = auto const lgrInfoOrStatus =
getLedgerInfoFromHashOrSeq(*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); getLedgerInfoFromHashOrSeq(*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus)) if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status}; return Error{*status};
@@ -83,14 +84,13 @@ NFTOffersHandlerBase::iterateOfferDirectory(
{ {
cursor = uint256(input.marker->c_str()); cursor = uint256(input.marker->c_str());
// We have a start point. Use limit - 1 from the result and use the // We have a start point. Use limit - 1 from the result and use the very last one for the resume.
// very last one for the resume.
auto const sle = [this, &cursor, &lgrInfo, &yield]() -> std::shared_ptr<SLE const> { auto const sle = [this, &cursor, &lgrInfo, &yield]() -> std::shared_ptr<SLE const> {
auto const key = keylet::nftoffer(cursor).key; auto const key = keylet::nftoffer(cursor).key;
if (auto const blob = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, yield); blob) if (auto const blob = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, yield); blob)
{
return std::make_shared<SLE const>(SerialIter{blob->data(), blob->size()}, key); return std::make_shared<SLE const>(SerialIter{blob->data(), blob->size()}, key);
}
return nullptr; return nullptr;
}(); }();
@@ -106,8 +106,7 @@ NFTOffersHandlerBase::iterateOfferDirectory(
} }
else else
{ {
// We have no start point, limit should be one higher than // We have no start point, limit should be one higher than requested.
// requested.
offers.reserve(++reserve); offers.reserve(++reserve);
} }
@@ -156,6 +155,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTOffersHandler
if (output.marker) if (output.marker)
object[JS(marker)] = *(output.marker); object[JS(marker)] = *(output.marker);
if (output.limit) if (output.limit)
object[JS(limit)] = *(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 NFTOffersHandlerBase::Input
tag_invoke(boost::json::value_to_tag<NFTOffersHandlerBase::Input>, boost::json::value const& jv) tag_invoke(boost::json::value_to_tag<NFTOffersHandlerBase::Input>, boost::json::value const& jv)
{ {
auto input = NFTOffersHandlerBase::Input{};
auto const& jsonObject = jv.as_object(); auto const& jsonObject = jv.as_object();
NFTOffersHandlerBase::Input input;
input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str(); input.nftID = jsonObject.at(JS(nft_id)).as_string().c_str();
if (jsonObject.contains(JS(ledger_hash))) if (jsonObject.contains(JS(ledger_hash)))
{
input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str();
}
if (jsonObject.contains(JS(ledger_index))) if (jsonObject.contains(JS(ledger_index)))
{ {
if (!jsonObject.at(JS(ledger_index)).is_string()) if (!jsonObject.at(JS(ledger_index)).is_string())
{
input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64();
}
else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") else if (jsonObject.at(JS(ledger_index)).as_string() != "validated")
{
input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str());
}
} }
if (jsonObject.contains(JS(marker))) if (jsonObject.contains(JS(marker)))
{
input.marker = jsonObject.at(JS(marker)).as_string().c_str(); input.marker = jsonObject.at(JS(marker)).as_string().c_str();
}
if (jsonObject.contains(JS(limit))) if (jsonObject.contains(JS(limit)))
{
input.limit = jsonObject.at(JS(limit)).as_int64(); input.limit = jsonObject.at(JS(limit)).as_int64();
}
return input; return input;
} }
} // namespace RPCng } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class NFTOffersHandlerBase class NFTOffersHandlerBase
{ {
@@ -51,7 +51,7 @@ public:
std::optional<std::string> marker; std::optional<std::string> marker;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
NFTOffersHandlerBase(std::shared_ptr<BackendInterface> const& sharedPtrBackend) NFTOffersHandlerBase(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -88,4 +88,4 @@ private:
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -18,21 +18,22 @@
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/NFTSellOffers.h> #include <rpc/handlers/NFTSellOffers.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
using namespace ripple; using namespace ripple;
namespace RPCng { namespace RPC {
NFTSellOffersHandler::Result NFTSellOffersHandler::Result
NFTSellOffersHandler::process(NFTSellOffersHandler::Input input, Context const& ctx) const NFTSellOffersHandler::process(NFTSellOffersHandler::Input input, Context const& ctx) const
{ {
auto const tokenID = uint256{input.nftID.c_str()}; auto const tokenID = uint256{input.nftID.c_str()};
auto const directory = keylet::nft_sells(tokenID); auto const directory = keylet::nft_sells(tokenID);
return iterateOfferDirectory(input, tokenID, directory, ctx.yield); return iterateOfferDirectory(input, tokenID, directory, ctx.yield);
} }
} // namespace RPCng } // namespace RPC

View File

@@ -20,9 +20,10 @@
#pragma once #pragma once
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/ngHandlers/NFTOffersCommon.h> #include <rpc/handlers/NFTOffersCommon.h>
namespace RPC {
namespace RPCng {
class NFTSellOffersHandler : public NFTOffersHandlerBase class NFTSellOffersHandler : public NFTOffersHandlerBase
{ {
public: public:
@@ -34,4 +35,5 @@ public:
Result Result
process(Input input, Context const& ctx) const; process(Input input, Context const& ctx) const;
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -18,96 +18,84 @@
//============================================================================== //==============================================================================
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPC.h>
#include <rpc/handlers/NoRippleCheck.h>
#include <fmt/core.h>
namespace RPC { namespace RPC {
boost::json::object NoRippleCheckHandler::Result
getBaseTx(ripple::AccountID const& accountID, std::uint32_t accountSeq, ripple::Fees const& fees) NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ctx) const
{ {
boost::json::object tx; auto const range = sharedPtrBackend_->fetchLedgerRange();
tx[JS(Sequence)] = accountSeq; auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
tx[JS(Account)] = ripple::toBase58(accountID); *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
tx[JS(Fee)] = RPC::toBoostJson(fees.units.jsonClipped());
return tx;
}
Result if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
doNoRippleCheck(Context const& context) return Error{*status};
{
auto const& request = context.params;
ripple::AccountID accountID; auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
if (auto const status = getAccount(request, accountID); status) auto const accountID = accountFromStringStrict(input.account);
return status; 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<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
std::optional<ripple::Fees> 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) if (!accountObj)
throw AccountNotFoundError(ripple::toBase58(accountID)); return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
ripple::SerialIter it{accountObj->data(), accountObj->size()}; auto it = ripple::SerialIter{accountObj->data(), accountObj->size()};
ripple::SLE sle{it, keylet.key}; 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; if (input.transactions)
bool bDefaultRipple = sle.getFieldU32(ripple::sfFlags) & ripple::lsfDefaultRipple; output.transactions.emplace(boost::json::array());
if (bDefaultRipple & !roleGateway)
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 appear to have set your default ripple flag even though "
"you " "you "
"are not a gateway. This is not recommended unless you are " "are not a gateway. This is not recommended unless you are "
"experimenting"); "experimenting");
} }
else if (roleGateway & !bDefaultRipple) else if (input.roleGateway && !bDefaultRipple)
{ {
problems.push_back("You should immediately set your default ripple flag"); output.problems.push_back("You should immediately set your default ripple flag");
if (includeTxs)
if (input.transactions)
{ {
auto tx = getBaseTx(accountID, accountSeq++, *fees); auto tx = getBaseTx(*accountID, accountSeq++);
tx[JS(TransactionType)] = JS(AccountSet); tx[JS(TransactionType)] = "AccountSet";
tx[JS(SetFlag)] = 8; tx[JS(SetFlag)] = ripple::asfDefaultRipple;
transactions.push_back(tx); output.transactions->push_back(tx);
} }
} }
traverseOwnedNodes( auto limit = input.limit;
*context.backend,
accountID, ngTraverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
context.yield, ctx.yield,
[roleGateway, includeTxs, &fees, &transactions, &accountSeq, &limit, &accountID, &problems]( [&](ripple::SLE&& ownedItem) {
ripple::SLE&& ownedItem) { // don't push to result if limit is reached
if (ownedItem.getType() == ripple::ltRIPPLE_STATE) if (limit != 0 && ownedItem.getType() == ripple::ltRIPPLE_STATE)
{ {
bool const bLow = accountID == ownedItem.getFieldAmount(ripple::sfLowLimit).getIssuer(); bool const bLow = accountID == ownedItem.getFieldAmount(ripple::sfLowLimit).getIssuer();
@@ -116,12 +104,12 @@ doNoRippleCheck(Context const& context)
std::string problem; std::string problem;
bool needFix = false; bool needFix = false;
if (bNoRipple & roleGateway) if (bNoRipple && input.roleGateway)
{ {
problem = "You should clear the no ripple flag on your "; problem = "You should clear the no ripple flag on your ";
needFix = true; needFix = true;
} }
else if (!bNoRipple & !roleGateway) else if (!bNoRipple && !input.roleGateway)
{ {
problem = problem =
"You should probably set the no ripple flag on " "You should probably set the no ripple flag on "
@@ -130,41 +118,85 @@ doNoRippleCheck(Context const& context)
} }
if (needFix) if (needFix)
{ {
--limit;
ripple::AccountID peer = ripple::AccountID peer =
ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit).getIssuer(); ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit).getIssuer();
ripple::STAmount peerLimit = ripple::STAmount peerLimit =
ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit);
problem += to_string(peerLimit.getCurrency());
problem += " line to "; problem += fmt::format(
problem += to_string(peerLimit.getIssuer()); "{} line to {}", to_string(peerLimit.getCurrency()), to_string(peerLimit.getIssuer()));
problems.emplace_back(problem); output.problems.emplace_back(problem);
if (includeTxs)
if (input.transactions)
{ {
ripple::STAmount limitAmount( ripple::STAmount limitAmount(
ownedItem.getFieldAmount(bLow ? ripple::sfLowLimit : ripple::sfHighLimit)); ownedItem.getFieldAmount(bLow ? ripple::sfLowLimit : ripple::sfHighLimit));
limitAmount.setIssuer(peer); 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) auto tx = getBaseTx(*accountID, accountSeq++);
return false;
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; return true;
}); });
boost::json::object response; output.ledgerIndex = lgrInfo.seq;
response[JS(ledger_index)] = lgrInfo.seq; output.ledgerHash = ripple::strHex(lgrInfo.hash);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["problems"] = std::move(problems);
if (includeTxs)
response[JS(transactions)] = std::move(transactions);
return response; return output;
}
NoRippleCheckHandler::Input
tag_invoke(boost::json::value_to_tag<NoRippleCheckHandler::Input>, 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 } // namespace RPC

View File

@@ -26,7 +26,7 @@
#include <set> #include <set>
namespace RPCng { namespace RPC {
class NoRippleCheckHandler class NoRippleCheckHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -52,7 +52,7 @@ public:
bool transactions = false; bool transactions = false;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
NoRippleCheckHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) NoRippleCheckHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -68,7 +68,7 @@ public:
validation::Required{}, validation::Required{},
validation::WithCustomError{ validation::WithCustomError{
validation::OneOf{"gateway", "user"}, 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_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator}, {JS(ledger_index), validation::LedgerIndexValidator},
{JS(limit), validation::Type<uint32_t>(), validation::Between{1, 500}}, {JS(limit), validation::Type<uint32_t>(), validation::Between{1, 500}},
@@ -88,4 +88,4 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -21,7 +21,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
namespace RPCng { namespace RPC {
class PingHandler class PingHandler
{ {
@@ -35,4 +35,5 @@ public:
return Output{}; return Output{};
} }
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,24 +17,29 @@
*/ */
//============================================================================== //==============================================================================
#include <cassert> #include <rpc/RPCHelpers.h>
#include <rpc/handlers/Random.h>
#include <ripple/beast/utility/rngfill.h> #include <ripple/beast/utility/rngfill.h>
#include <ripple/crypto/csprng.h> #include <ripple/crypto/csprng.h>
#include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
Result RandomHandler::Result
doRandom(Context const& context) RandomHandler::process() const
{ {
ripple::uint256 rand; ripple::uint256 rand;
beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng()); beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng());
boost::json::object result;
result[JS(random)] = ripple::strHex(rand); return Output{ripple::strHex(rand)};
return result; }
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, RandomHandler::Output const& output)
{
jv = {
{JS(random), output.random},
};
} }
} // namespace RPC } // namespace RPC

View File

@@ -25,7 +25,7 @@
#include <string> #include <string>
namespace RPCng { namespace RPC {
class RandomHandler class RandomHandler
{ {
@@ -44,4 +44,5 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -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 <backend/BackendInterface.h>
#include <etl/ETLSource.h>
#include <etl/ReportingETL.h>
#include <main/Build.h>
#include <rpc/RPCHelpers.h>
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::seconds>(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

View File

@@ -37,7 +37,7 @@ namespace RPC {
class Counters; class Counters;
} }
namespace RPCng { namespace RPC {
template < template <
typename SubscriptionManagerType, typename SubscriptionManagerType,
@@ -133,19 +133,20 @@ public:
if (not fees.has_value()) if (not fees.has_value())
return Error{Status{RippledError::rpcINTERNAL}}; return Error{Status{RippledError::rpcINTERNAL}};
auto output = Output{};
auto const sinceEpoch = duration_cast<seconds>(system_clock::now().time_since_epoch()).count(); auto const sinceEpoch = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
auto const age = static_cast<int32_t>(sinceEpoch) - auto const age = static_cast<int32_t>(sinceEpoch) -
static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) - static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
static_cast<int32_t>(rippleEpochStart); static_cast<int32_t>(rippleEpochStart);
Output output;
output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence); output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
if (ctx.isAdmin) if (ctx.isAdmin)
output.info.adminSection = {counters_.get().report(), subscriptions_->report(), etl_->getInfo()}; output.info.adminSection = {counters_.get().report(), subscriptions_->report(), etl_->getInfo()};
auto const serverInfoRippled = auto const serverInfoRippled =
balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield); balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield);
if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) if (serverInfoRippled && !serverInfoRippled->contains(JS(error)))
{ {
if (serverInfoRippled->contains(JS(result)) && if (serverInfoRippled->contains(JS(result)) &&
@@ -159,7 +160,6 @@ public:
output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash); output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash);
output.info.validatedLedger.seq = lgrInfo->seq; output.info.validatedLedger.seq = lgrInfo->seq;
output.info.validatedLedger.fees = fees; output.info.validatedLedger.fees = fees;
output.info.cache.size = backend_->cache().size(); output.info.cache.size = backend_->cache().size();
output.info.cache.isFull = backend_->cache().isFull(); output.info.cache.isFull = backend_->cache().isFull();
output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence(); output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
@@ -195,6 +195,7 @@ private:
try try
{ {
auto const& rippledInfo = info.rippledInfo.value(); auto const& rippledInfo = info.rippledInfo.value();
jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor)); 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()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version)); jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version));
@@ -238,6 +239,6 @@ private:
} }
}; };
using ServerInfoHandler = BaseServerInfoHandler<SubscriptionManager, ETLLoadBalancer, ReportingETL, RPC::Counters>; using ServerInfoHandler = BaseServerInfoHandler<SubscriptionManager, ETLLoadBalancer, ReportingETL, Counters>;
} // namespace RPCng } // namespace RPC

View File

@@ -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 <boost/json.hpp>
#include <subscriptions/SubscriptionManager.h>
#include <webserver/WsBase.h>
#include <rpc/RPCHelpers.h>
namespace RPC {
// these are the streams that take no arguments
static std::unordered_set<std::string>
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<WsBase> 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<WsBase> 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<WsBase> 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<WsBase> 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<WsBase> 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<ripple::AccountID>(s);
if (!accountID)
{
assert(false);
continue;
}
manager.subProposedAccount(*accountID, session);
}
}
void
unsubscribeToAccountsProposed(
boost::json::object const& request,
std::shared_ptr<WsBase> 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<ripple::AccountID>(s);
if (!accountID)
{
assert(false);
continue;
}
manager.unsubProposedAccount(*accountID, session);
}
}
std::variant<Status, std::pair<std::vector<ripple::Book>, boost::json::array>>
validateAndGetBooks(
boost::asio::yield_context& yield,
boost::json::object const& request,
std::shared_ptr<Backend::BackendInterface const> 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<ripple::Book> booksToSub;
std::optional<Backend::LedgerRange> rng;
boost::json::array snapshot;
for (auto const& book : books)
{
auto parsedBook = parseBook(book.as_object());
if (auto status = std::get_if<Status>(&parsedBook))
return *status;
auto b = std::get<ripple::Book>(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<ripple::Book> const& books, std::shared_ptr<WsBase> session, SubscriptionManager& manager)
{
for (auto const& book : books)
{
manager.subBook(book, session);
}
}
void
unsubscribeToBooks(
std::vector<ripple::Book> const& books,
std::shared_ptr<WsBase> 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<ripple::Book> books;
boost::json::object response;
if (request.contains(JS(books)))
{
auto parsed = validateAndGetBooks(context.yield, request, context.backend);
if (auto status = std::get_if<Status>(&parsed))
return *status;
auto [bks, snap] = std::get<std::pair<std::vector<ripple::Book>, 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<ripple::Book> books;
if (request.contains(JS(books)))
{
auto parsed = validateAndGetBooks(context.yield, request, context.backend);
if (auto status = std::get_if<Status>(&parsed))
return *status;
auto [bks, snap] = std::get<std::pair<std::vector<ripple::Book>, 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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
template <typename SubscriptionManagerType> template <typename SubscriptionManagerType>
class BaseSubscribeHandler class BaseSubscribeHandler
{ {
@@ -40,7 +40,6 @@ public:
// books returns nothing by default, if snapshot is true, it returns offers // books returns nothing by default, if snapshot is true, it returns offers
// TODO: use better type than json // TODO: use better type than json
std::optional<boost::json::array> offers; std::optional<boost::json::array> offers;
bool validated = true;
}; };
struct OrderBook struct OrderBook
@@ -59,7 +58,7 @@ public:
std::optional<std::vector<OrderBook>> books; std::optional<std::vector<OrderBook>> books;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
BaseSubscribeHandler( BaseSubscribeHandler(
std::shared_ptr<BackendInterface> const& sharedPtrBackend, std::shared_ptr<BackendInterface> const& sharedPtrBackend,
@@ -74,27 +73,28 @@ public:
static auto const booksValidator = static auto const booksValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array()) 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()) for (auto const& book : value.as_array())
{ {
if (!book.is_object()) if (!book.is_object())
return Error{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool()) 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()) 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 (book.as_object().contains("taker"))
if (auto const err = validation::AccountValidator.verify(book.as_object(), "taker"); !err) if (auto const err = validation::AccountValidator.verify(book.as_object(), "taker"); !err)
return err; return err;
auto const parsedBook = RPC::parseBook(book.as_object()); auto const parsedBook = parseBook(book.as_object());
if (auto const status = std::get_if<RPC::Status>(&parsedBook)) if (auto const status = std::get_if<Status>(&parsedBook))
return Error(*status); return Error(*status);
} }
return MaybeError{}; return MaybeError{};
}}; }};
@@ -102,30 +102,37 @@ public:
{JS(streams), validation::SubscribeStreamValidator}, {JS(streams), validation::SubscribeStreamValidator},
{JS(accounts), validation::SubscribeAccountsValidator}, {JS(accounts), validation::SubscribeAccountsValidator},
{JS(accounts_proposed), validation::SubscribeAccountsValidator}, {JS(accounts_proposed), validation::SubscribeAccountsValidator},
{JS(books), booksValidator}}; {JS(books), booksValidator},
};
return rpcSpec; return rpcSpec;
} }
Result Result
process(Input input, Context const& ctx) const process(Input input, Context const& ctx) const
{ {
Output output; auto output = Output{};
if (input.streams) if (input.streams)
{ {
auto const ledger = subscribeToStreams(ctx.yield, *(input.streams), ctx.session); auto const ledger = subscribeToStreams(ctx.yield, *(input.streams), ctx.session);
if (!ledger.empty()) if (!ledger.empty())
output.ledger = ledger; output.ledger = ledger;
} }
if (input.accounts) if (input.accounts)
subscribeToAccounts(*(input.accounts), ctx.session); subscribeToAccounts(*(input.accounts), ctx.session);
if (input.accountsProposed) if (input.accountsProposed)
subscribeToAccountsProposed(*(input.accountsProposed), ctx.session); subscribeToAccountsProposed(*(input.accountsProposed), ctx.session);
if (input.books) if (input.books)
{ {
auto const offers = subscribeToBooks(*(input.books), ctx.session, ctx.yield); auto const offers = subscribeToBooks(*(input.books), ctx.session, ctx.yield);
if (!offers.empty()) if (!offers.empty())
output.offers = offers; output.offers = offers;
}; };
return output; return output;
} }
@@ -136,7 +143,8 @@ private:
std::vector<std::string> const& streams, std::vector<std::string> const& streams,
std::shared_ptr<WsBase> const& session) const std::shared_ptr<WsBase> const& session) const
{ {
boost::json::object response; auto response = boost::json::object{};
for (auto const& stream : streams) for (auto const& stream : streams)
{ {
if (stream == "ledger") if (stream == "ledger")
@@ -152,6 +160,7 @@ private:
else if (stream == "book_changes") else if (stream == "book_changes")
subscriptions_->subBookChanges(session); subscriptions_->subBookChanges(session);
} }
return response; return response;
} }
@@ -160,16 +169,17 @@ private:
{ {
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
auto const accountID = RPC::accountFromStringStrict(account); auto const accountID = accountFromStringStrict(account);
subscriptions_->subAccount(*accountID, session); subscriptions_->subAccount(*accountID, session);
} }
} }
void void
subscribeToAccountsProposed(std::vector<std::string> const& accounts, std::shared_ptr<WsBase> const& session) const subscribeToAccountsProposed(std::vector<std::string> const& accounts, std::shared_ptr<WsBase> const& session) const
{ {
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
auto const accountID = RPC::accountFromStringStrict(account); auto const accountID = accountFromStringStrict(account);
subscriptions_->subProposedAccount(*accountID, session); subscriptions_->subProposedAccount(*accountID, session);
} }
} }
@@ -180,9 +190,10 @@ private:
std::shared_ptr<WsBase> const& session, std::shared_ptr<WsBase> const& session,
boost::asio::yield_context& yield) const boost::asio::yield_context& yield) const
{ {
static auto constexpr fetchLimit = 200;
boost::json::array snapshots; boost::json::array snapshots;
std::optional<Backend::LedgerRange> rng; std::optional<Backend::LedgerRange> rng;
static auto constexpr fetchLimit = 200;
for (auto const& internalBook : books) for (auto const& internalBook : books)
{ {
@@ -190,6 +201,7 @@ private:
{ {
if (!rng) if (!rng)
rng = sharedPtrBackend_->fetchLedgerRange(); rng = sharedPtrBackend_->fetchLedgerRange();
auto const getOrderBook = [&](auto const& book) { auto const getOrderBook = [&](auto const& book) {
auto const bookBase = getBookBase(book); auto const bookBase = getBookBase(book);
auto const [offers, _] = auto const [offers, _] =
@@ -198,18 +210,21 @@ private:
// the taker is not really uesed, same issue with // the taker is not really uesed, same issue with
// https://github.com/XRPLF/xrpl-dev-portal/issues/1818 // https://github.com/XRPLF/xrpl-dev-portal/issues/1818
auto const takerID = auto const takerID =
internalBook.taker ? RPC::accountFromStringStrict(*(internalBook.taker)) : beast::zero; internalBook.taker ? accountFromStringStrict(*(internalBook.taker)) : beast::zero;
auto const orderBook = 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)); std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshots));
}; };
getOrderBook(internalBook.book); getOrderBook(internalBook.book);
if (internalBook.both) if (internalBook.both)
getOrderBook(ripple::reversed(internalBook.book)); getOrderBook(ripple::reversed(internalBook.book));
} }
subscriptions_->subBook(internalBook.book, session); subscriptions_->subBook(internalBook.book, session);
if (internalBook.both) if (internalBook.both)
subscriptions_->subBook(ripple::reversed(internalBook.book), session); 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) tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
{ {
jv = output.ledger ? *(output.ledger) : boost::json::object(); jv = output.ledger ? *(output.ledger) : boost::json::object();
if (output.offers) if (output.offers)
jv.as_object().emplace(JS(offers), *(output.offers)); jv.as_object().emplace(JS(offers), *(output.offers));
} }
@@ -228,48 +244,57 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv) tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
{ {
auto input = Input{};
auto const& jsonObject = jv.as_object(); auto const& jsonObject = jv.as_object();
Input input;
if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end())
{ {
input.streams = std::vector<std::string>(); input.streams = std::vector<std::string>();
for (auto const& stream : streams->value().as_array()) for (auto const& stream : streams->value().as_array())
input.streams->push_back(stream.as_string().c_str()); input.streams->push_back(stream.as_string().c_str());
} }
if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end()) if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end())
{ {
input.accounts = std::vector<std::string>(); input.accounts = std::vector<std::string>();
for (auto const& account : accounts->value().as_array()) for (auto const& account : accounts->value().as_array())
input.accounts->push_back(account.as_string().c_str()); input.accounts->push_back(account.as_string().c_str());
} }
if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed)); accountsProposed != jsonObject.end()) if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed)); accountsProposed != jsonObject.end())
{ {
input.accountsProposed = std::vector<std::string>(); input.accountsProposed = std::vector<std::string>();
for (auto const& account : accountsProposed->value().as_array()) for (auto const& account : accountsProposed->value().as_array())
input.accountsProposed->push_back(account.as_string().c_str()); input.accountsProposed->push_back(account.as_string().c_str());
} }
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end())
{ {
input.books = std::vector<OrderBook>(); input.books = std::vector<OrderBook>();
for (auto const& book : books->value().as_array()) for (auto const& book : books->value().as_array())
{ {
auto internalBook = OrderBook{};
auto const& bookObject = book.as_object(); auto const& bookObject = book.as_object();
OrderBook internalBook;
if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end()) if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end())
internalBook.taker = taker->value().as_string().c_str(); internalBook.taker = taker->value().as_string().c_str();
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end()) if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
internalBook.both = both->value().as_bool(); internalBook.both = both->value().as_bool();
if (auto const& snapshot = bookObject.find(JS(snapshot)); snapshot != bookObject.end()) if (auto const& snapshot = bookObject.find(JS(snapshot)); snapshot != bookObject.end())
internalBook.snapshot = snapshot->value().as_bool(); 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<ripple::Book>(parsedBookMaybe); internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
input.books->push_back(internalBook); input.books->push_back(internalBook);
} }
} }
return input; return input;
} }
}; };
using SubscribeHandler = BaseSubscribeHandler<SubscriptionManager>; using SubscribeHandler = BaseSubscribeHandler<SubscriptionManager>;
} // namespace RPCng } // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,25 +17,22 @@
*/ */
//============================================================================== //==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/handlers/TransactionEntry.h>
namespace RPC { namespace RPC {
Result TransactionEntryHandler::Result
doTransactionEntry(Context const& context) TransactionEntryHandler::process(TransactionEntryHandler::Input input, Context const& ctx) const
{ {
boost::json::object response; auto const range = sharedPtrBackend_->fetchLedgerRange();
auto v = ledgerInfoFromRequest(context); auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
if (auto status = std::get_if<Status>(&v)) *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v); if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
ripple::uint256 hash; auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash)))) auto const dbRet = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.txHash.c_str()}, ctx.yield);
return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
// Note: transaction_entry is meant to only search a specified ledger for // Note: transaction_entry is meant to only search a specified ledger for
// the specified transaction. tx searches the entire range of history. For // the specified transaction. tx searches the entire range of history. For
// rippled, having two separate commands made sense, as tx would use SQLite // 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 // the API for transaction_entry says the method only searches the specified
// ledger; we simulate that here by returning not found if the transaction // ledger; we simulate that here by returning not found if the transaction
// is in a different ledger than the one specified. // is in a different ledger than the one specified.
if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq) if (!dbRet || dbRet->ledgerSequence != lgrInfo.seq)
return Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}; return Error{Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}};
auto [txn, meta] = toExpandedJson(*dbResponse); auto output = TransactionEntryHandler::Output{};
response[JS(tx_json)] = std::move(txn); auto [txn, meta] = toExpandedJson(*dbRet);
response[JS(metadata)] = std::move(meta);
response[JS(ledger_index)] = lgrInfo.seq; output.tx = std::move(txn);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); output.metadata = std::move(meta);
return response; 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<TransactionEntryHandler::Input>, 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 } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class TransactionEntryHandler class TransactionEntryHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -48,7 +48,7 @@ public:
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
TransactionEntryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) TransactionEntryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
@@ -77,4 +77,5 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng
} // namespace RPC

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of clio: https://github.com/XRPLF/clio 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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,84 +17,106 @@
*/ */
//============================================================================== //==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/handlers/Tx.h>
namespace RPC { namespace RPC {
// { TxHandler::Result
// transaction: <hex> TxHandler::process(Input input, Context const& ctx) const
// }
Result
doTx(Context const& context)
{ {
auto request = context.params; constexpr static auto maxLedgerRange = 1000u;
boost::json::object response = {}; auto const rangeSupplied = input.minLedger && input.maxLedger;
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;
if (rangeSupplied) if (rangeSupplied)
{ {
if (*minLedger > *maxLedger) if (*input.minLedger > *input.maxLedger)
return Status{RippledError::rpcINVALID_LGR_RANGE}; return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
if (*maxLedger - *minLedger > 1000)
return Status{RippledError::rpcEXCESSIVE_LGR_RANGE}; if (*input.maxLedger - *input.minLedger > maxLedgerRange)
return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
} }
auto range = context.backend->fetchLedgerRange(); auto output = TxHandler::Output{};
if (!range) auto const dbResponse =
return Status{RippledError::rpcNOT_READY}; sharedPtrBackend_->fetchTransaction(ripple::uint256{std::string_view(input.transaction)}, ctx.yield);
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
if (!dbResponse) if (!dbResponse)
{ {
if (rangeSupplied) 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; boost::json::object extra;
extra["searched_all"] = searchedAll; 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); auto const [txn, meta] = toExpandedJson(*dbResponse);
response = txn; output.tx = txn;
response[JS(meta)] = meta; output.meta = meta;
} }
else else
{ {
response[JS(tx)] = ripple::strHex(dbResponse->transaction); output.txStr = ripple::strHex(dbResponse->transaction);
response[JS(meta)] = ripple::strHex(dbResponse->metadata); output.metaStr = ripple::strHex(dbResponse->metadata);
response[JS(hash)] = std::move(request.at(JS(transaction)).as_string()); 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<TxHandler::Input>, 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 } // namespace RPC

View File

@@ -23,7 +23,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
class TxHandler class TxHandler
{ {
std::shared_ptr<BackendInterface> sharedPtrBackend_; std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -50,7 +50,7 @@ public:
std::optional<uint32_t> maxLedger; std::optional<uint32_t> maxLedger;
}; };
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
TxHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) TxHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
@@ -79,4 +79,4 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv); tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
}; };
} // namespace RPCng } // namespace RPC

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
namespace RPCng { namespace RPC {
template <typename SubscriptionManagerType> template <typename SubscriptionManagerType>
class BaseUnsubscribeHandler class BaseUnsubscribeHandler
@@ -48,7 +48,7 @@ public:
}; };
using Output = VoidOutput; using Output = VoidOutput;
using Result = RPCng::HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
BaseUnsubscribeHandler( BaseUnsubscribeHandler(
std::shared_ptr<BackendInterface> const& sharedPtrBackend, std::shared_ptr<BackendInterface> const& sharedPtrBackend,
@@ -63,21 +63,21 @@ public:
static auto const booksValidator = static auto const booksValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array()) 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()) for (auto const& book : value.as_array())
{ {
if (!book.is_object()) if (!book.is_object())
return Error{ return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool()) 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()); auto const parsedBook = parseBook(book.as_object());
if (auto const status = std::get_if<RPC::Status>(&parsedBook)) if (auto const status = std::get_if<Status>(&parsedBook))
return Error(*status); return Error(*status);
} }
return MaybeError{}; return MaybeError{};
}}; }};
@@ -85,7 +85,8 @@ public:
{JS(streams), validation::SubscribeStreamValidator}, {JS(streams), validation::SubscribeStreamValidator},
{JS(accounts), validation::SubscribeAccountsValidator}, {JS(accounts), validation::SubscribeAccountsValidator},
{JS(accounts_proposed), validation::SubscribeAccountsValidator}, {JS(accounts_proposed), validation::SubscribeAccountsValidator},
{JS(books), booksValidator}}; {JS(books), booksValidator},
};
return rpcSpec; return rpcSpec;
} }
@@ -136,7 +137,7 @@ private:
{ {
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
auto accountID = RPC::accountFromStringStrict(account); auto const accountID = accountFromStringStrict(account);
subscriptions_->unsubAccount(*accountID, session); subscriptions_->unsubAccount(*accountID, session);
} }
} }
@@ -147,7 +148,7 @@ private:
{ {
for (auto const& account : accountsProposed) for (auto const& account : accountsProposed)
{ {
auto accountID = RPC::accountFromStringStrict(account); auto const accountID = accountFromStringStrict(account);
subscriptions_->unsubProposedAccount(*accountID, session); subscriptions_->unsubProposedAccount(*accountID, session);
} }
} }
@@ -158,6 +159,7 @@ private:
for (auto const& orderBook : books) for (auto const& orderBook : books)
{ {
subscriptions_->unsubBook(orderBook.book, session); subscriptions_->unsubBook(orderBook.book, session);
if (orderBook.both) if (orderBook.both)
subscriptions_->unsubBook(ripple::reversed(orderBook.book), session); subscriptions_->unsubBook(ripple::reversed(orderBook.book), session);
} }
@@ -166,8 +168,9 @@ private:
friend Input friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv) tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
{ {
auto input = Input{};
auto const& jsonObject = jv.as_object(); auto const& jsonObject = jv.as_object();
Input input;
if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end())
{ {
input.streams = std::vector<std::string>(); input.streams = std::vector<std::string>();
@@ -191,19 +194,22 @@ private:
input.books = std::vector<OrderBook>(); input.books = std::vector<OrderBook>();
for (auto const& book : books->value().as_array()) for (auto const& book : books->value().as_array())
{ {
auto internalBook = OrderBook{};
auto const& bookObject = book.as_object(); auto const& bookObject = book.as_object();
OrderBook internalBook;
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end()) if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
internalBook.both = both->value().as_bool(); 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<ripple::Book>(parsedBookMaybe); internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
input.books->push_back(internalBook); input.books->push_back(internalBook);
} }
} }
return input; return input;
} }
}; };
using UnsubscribeHandler = BaseUnsubscribeHandler<SubscriptionManager>; using UnsubscribeHandler = BaseUnsubscribeHandler<SubscriptionManager>;
} // namespace RPCng } // namespace RPC

View File

@@ -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 <rpc/RPC.h>
#include <rpc/ngHandlers/AccountChannels.h>
namespace RPCng {
void
AccountChannelsHandler::addChannel(std::vector<ChannelResponse>& 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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<ripple::AccountID>{};
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<RPC::AccountCursor>(next);
if (nextMarker.isNonZero())
response.marker = nextMarker.toString();
return response;
}
AccountChannelsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/AccountCurrencies.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<std::uint32_t>::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<AccountCurrenciesHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/AccountInfo.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<ripple::STLedgerEntry> 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<AccountInfoHandler::Input>, 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

View File

@@ -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 <rpc/RPC.h>
#include <rpc/ngHandlers/AccountLines.h>
namespace RPCng {
void
AccountLinesHandler::addLine(
std::vector<LineResponse>& lines,
ripple::SLE const& lineSle,
ripple::AccountID const& account,
std::optional<ripple::AccountID> 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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<ripple::AccountID>{};
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<RPC::AccountCursor>(next);
if (nextMarker.isNonZero())
response.marker = nextMarker.toString();
return response;
}
AccountLinesHandler::Input
tag_invoke(boost::json::value_to_tag<AccountLinesHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/AccountObjects.h>
namespace RPCng {
// document does not mention nft_page, we still support it tho
std::unordered_map<std::string, ripple::LedgerEntryType> 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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<RPC::Status>(&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<RPC::AccountCursor>(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<AccountObjectsHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/AccountOffers.h>
namespace RPCng {
void
AccountOffersHandler::addOffer(std::vector<Offer>& 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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<RPC::Status>(&next))
return Error{*status};
auto const nextMarker = std::get<RPC::AccountCursor>(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<AccountOffersHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/AccountTx.h>
#include <util/Profiler.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
maxIndex = minIndex = std::get<ripple::LedgerInfo>(lgrInfoOrStatus).seq;
}
std::optional<Backend::TransactionsCursor> 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<AccountTxHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/BookChanges.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<BookChangesHandler::Input>, 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

View File

@@ -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 <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/BookOffers.h>
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<RPC::Status>(&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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const book = std::get<ripple::Book>(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<BookOffersHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/GatewayBalances.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
// check account
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<std::uint32_t>::max(),
{},
ctx.yield,
addToResponse);
if (auto status = std::get_if<RPC::Status>(&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<ripple::AccountID, std::vector<ripple::STAmount>> 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<GatewayBalancesHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/Ledger.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<LedgerHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/LedgerData.h>
#include <ripple/app/ledger/LedgerToJson.h>
#include <algorithm>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<Backend::LedgerObject> results;
if (input.diffMarker)
{
// keep the same logic as previous implementation
auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield);
std::vector<ripple::uint256> 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<std::chrono::microseconds>(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<std::chrono::microseconds>(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<LedgerDataHandler::Input>, 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

View File

@@ -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 <rpc/ngHandlers/LedgerEntry.h>
#include <unordered_map>
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<ripple::AccountID>(*(input.accountRoot))).key;
}
else if (input.directory)
{
auto const keyOrStatus = composeKeyFromDirectory(*input.directory);
if (auto const status = std::get_if<RPC::Status>(&keyOrStatus))
return Error{*status};
key = std::get<ripple::uint256>(keyOrStatus);
}
else if (input.offer)
{
auto const id = ripple::parseBase58<ripple::AccountID>(input.offer->at(JS(account)).as_string().c_str());
key = ripple::keylet::offer(*id, boost::json::value_to<std::uint32_t>(input.offer->at(JS(seq)))).key;
}
else if (input.rippleStateAccount)
{
auto const id1 = ripple::parseBase58<ripple::AccountID>(
input.rippleStateAccount->at(JS(accounts)).as_array().at(0).as_string().c_str());
auto const id2 = ripple::parseBase58<ripple::AccountID>(
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<ripple::AccountID>(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<ripple::AccountID>(input.depositPreauth->at(JS(owner)).as_string().c_str());
auto const authorized =
ripple::parseBase58<ripple::AccountID>(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<ripple::AccountID>(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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(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<ripple::uint256, RPC::Status>
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<uint64_t>(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<ripple::AccountID>(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<LedgerEntryHandler::Input>, 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<std::string, ripple::LedgerEntryType>{
{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

View File

@@ -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 <rpc/ngHandlers/NFTHistory.h>
#include <util/Profiler.h>
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<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
maxIndex = minIndex = std::get<ripple::LedgerInfo>(lgrInfoOrStatus).seq;
}
std::optional<Backend::TransactionsCursor> 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<NFTHistoryHandler::Input>, 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

View File

@@ -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 <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/NFTInfo.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h>
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<Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<LedgerInfo>(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<NFTInfoHandler::Input>, 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

Some files were not shown because too many files have changed in this diff Show More