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

View File

@@ -65,69 +65,36 @@ target_sources(clio PRIVATE
src/rpc/RPCHelpers.cpp
src/rpc/Counters.cpp
src/rpc/WorkQueue.cpp
## NextGen RPC
src/rpc/common/Specs.cpp
src/rpc/common/Validators.cpp
## NextGen RPC handler
src/rpc/ngHandlers/AccountChannels.cpp
src/rpc/ngHandlers/AccountCurrencies.cpp
src/rpc/ngHandlers/AccountLines.cpp
src/rpc/ngHandlers/AccountTx.cpp
src/rpc/ngHandlers/AccountOffers.cpp
src/rpc/ngHandlers/AccountInfo.cpp
src/rpc/ngHandlers/BookOffers.cpp
src/rpc/ngHandlers/GatewayBalances.cpp
src/rpc/ngHandlers/LedgerEntry.cpp
src/rpc/ngHandlers/LedgerRange.cpp
src/rpc/ngHandlers/TransactionEntry.cpp
src/rpc/ngHandlers/Tx.cpp
src/rpc/ngHandlers/Random.cpp
src/rpc/ngHandlers/NoRippleCheck.cpp
src/rpc/ngHandlers/NFTInfo.cpp
src/rpc/ngHandlers/NFTOffersCommon.cpp
src/rpc/ngHandlers/NFTBuyOffers.cpp
src/rpc/ngHandlers/NFTSellOffers.cpp
src/rpc/ngHandlers/NFTHistory.cpp
src/rpc/ngHandlers/LedgerData.cpp
src/rpc/ngHandlers/AccountNFTs.cpp
src/rpc/ngHandlers/AccountObjects.cpp
src/rpc/ngHandlers/BookChanges.cpp
src/rpc/ngHandlers/Ledger.cpp
## RPC Methods
# Account
# RPC impl
src/rpc/common/impl/HandlerProvider.cpp
## RPC handler
src/rpc/handlers/AccountChannels.cpp
src/rpc/handlers/AccountCurrencies.cpp
src/rpc/handlers/AccountInfo.cpp
src/rpc/handlers/AccountLines.cpp
src/rpc/handlers/AccountOffers.cpp
src/rpc/handlers/AccountNFTs.cpp
src/rpc/handlers/AccountObjects.cpp
src/rpc/handlers/AccountOffers.cpp
src/rpc/handlers/AccountTx.cpp
src/rpc/handlers/BookChanges.cpp
src/rpc/handlers/BookOffers.cpp
src/rpc/handlers/GatewayBalances.cpp
src/rpc/handlers/NoRippleCheck.cpp
# NFT
src/rpc/handlers/NFTHistory.cpp
src/rpc/handlers/NFTInfo.cpp
src/rpc/handlers/NFTOffers.cpp
# Ledger
src/rpc/handlers/Ledger.cpp
src/rpc/handlers/LedgerData.cpp
src/rpc/handlers/LedgerEntry.cpp
src/rpc/handlers/LedgerRange.cpp
# Transaction
src/rpc/handlers/Tx.cpp
src/rpc/handlers/TransactionEntry.cpp
src/rpc/handlers/AccountTx.cpp
# Dex
src/rpc/handlers/BookChanges.cpp
src/rpc/handlers/BookOffers.cpp
# Payment Channel
src/rpc/handlers/ChannelAuthorize.cpp
src/rpc/handlers/ChannelVerify.cpp
# Subscribe
src/rpc/handlers/Subscribe.cpp
# Server
src/rpc/handlers/ServerInfo.cpp
# Utilities
src/rpc/handlers/NFTBuyOffers.cpp
src/rpc/handlers/NFTHistory.cpp
src/rpc/handlers/NFTInfo.cpp
src/rpc/handlers/NFTOffersCommon.cpp
src/rpc/handlers/NFTSellOffers.cpp
src/rpc/handlers/NoRippleCheck.cpp
src/rpc/handlers/Random.cpp
src/rpc/handlers/TransactionEntry.cpp
src/rpc/handlers/Tx.cpp
## Util
src/config/Config.cpp
src/log/Logger.cpp
src/util/Taggable.cpp)

View File

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

View File

@@ -23,7 +23,7 @@
#include <set>
namespace RPCng {
namespace RPC {
/**
* @brief Represents an entry in the book_changes' changes array.
@@ -229,4 +229,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChange const
};
}
} // namespace RPCng
[[nodiscard]] boost::json::object const
computeBookChanges(ripple::LedgerInfo const& lgrInfo, std::vector<Backend::TransactionAndMetadata> const& transactions);
} // namespace RPC

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,33 +17,43 @@
*/
//==============================================================================
#include <rpc/RPCHelpers.h>
#include <rpc/ngHandlers/LedgerRange.h>
#pragma once
#include <rpc/common/AnyHandler.h>
#include <rpc/common/Types.h>
#include <memory>
#include <optional>
#include <string>
namespace RPCng {
namespace RPC {
LedgerRangeHandler::Result
LedgerRangeHandler::process() const
class HandlerTable
{
if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange)
{
return Output{*maybeRange};
}
else
{
return Error{RPC::Status{RPC::RippledError::rpcNOT_READY, "rangeNotFound"}};
}
}
std::shared_ptr<HandlerProvider const> provider_;
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerRangeHandler::Output const& output)
{
jv = boost::json::object{
{JS(ledger_index_min), output.range.minSequence},
{JS(ledger_index_max), output.range.maxSequence},
};
}
public:
HandlerTable(std::shared_ptr<HandlerProvider const> const& provider) : provider_{provider}
{
}
} // 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>
#include <rpc/ngHandlers/Random.h>
#pragma once
#include <ripple/beast/utility/rngfill.h>
#include <ripple/crypto/csprng.h>
#include <ripple/protocol/jss.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
RandomHandler::process() const
{
ripple::uint256 rand;
beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng());
return Output{ripple::strHex(rand)};
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, RandomHandler::Output const& output)
{
jv = {
{JS(random), output.random},
};
}
} // namespace RPCng
// Access (SF)ield name (S)trings
#define SFS(x) ripple::x.jsonName.c_str()

View File

@@ -19,8 +19,8 @@
#include <etl/ETLSource.h>
#include <log/Logger.h>
#include <rpc/Handlers.h>
#include <rpc/RPCHelpers.h>
#include <rpc/common/impl/HandlerProvider.h>
#include <webserver/HttpBase.h>
#include <webserver/WsBase.h>
@@ -30,6 +30,7 @@
using namespace std;
using namespace clio;
using namespace RPC;
// local to compilation unit loggers
namespace {
@@ -38,49 +39,14 @@ clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
Context::Context(
boost::asio::yield_context& yield_,
string const& command_,
uint32_t version_,
boost::json::object const& params_,
shared_ptr<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(
boost::asio::yield_context& yc,
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,
util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range,
Counters& counters,
string const& clientIp)
{
boost::json::value commandValue = nullptr;
@@ -93,22 +59,15 @@ make_WsContext(
return {};
string command = commandValue.as_string().c_str();
return make_optional<Context>(
yc, command, 1, request, backend, subscriptions, balancer, etl, session, tagFactory, range, counters, clientIp);
return make_optional<Web::Context>(yc, command, 1, request, session, tagFactory, range, clientIp);
}
optional<Context>
optional<Web::Context>
make_HttpContext(
boost::asio::yield_context& yc,
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,
Backend::LedgerRange const& range,
RPC::Counters& counters,
string const& clientIp)
{
if (!request.contains("method") || !request.at("method").is_string())
@@ -130,105 +89,9 @@ make_HttpContext(
if (!array.at(0).is_object())
return {};
return make_optional<Context>(
yc,
command,
1,
array.at(0).as_object(),
backend,
subscriptions,
balancer,
etl,
nullptr,
tagFactory,
range,
counters,
clientIp);
return make_optional<Web::Context>(yc, command, 1, array.at(0).as_object(), nullptr, tagFactory, range, 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{
"submit",
"submit_multisigned",
@@ -240,57 +103,55 @@ static unordered_set<string> forwardCommands{
"channel_authorize",
"channel_verify"};
bool
validHandler(string const& method)
RPCEngine::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)
: 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
isClioOnly(string const& method)
RPCEngine::validHandler(string const& method) const
{
return handlerTable.isClioOnly(method);
return handlerTable_.contains(method) || forwardCommands.contains(method);
}
bool
shouldSuppressValidatedFlag(RPC::Context const& context)
RPCEngine::isClioOnly(string const& method) const
{
return boost::iequals(context.method, "subscribe") || boost::iequals(context.method, "unsubscribe");
}
Status
getLimit(RPC::Context const& context, uint32_t& limit)
{
if (!handlerTable.getHandler(context.method))
return Status{RippledError::rpcUNKNOWN_COMMAND};
if (!handlerTable.getLimitRange(context.method))
return Status{RippledError::rpcINVALID_PARAMS, "rpcDoesNotRequireLimit"};
auto [lo, def, hi] = *handlerTable.getLimitRange(context.method);
if (context.params.contains(JS(limit)))
{
string errMsg = "Invalid field 'limit', not unsigned integer.";
if (!context.params.at(JS(limit)).is_int64())
return Status{RippledError::rpcINVALID_PARAMS, errMsg};
int input = context.params.at(JS(limit)).as_int64();
if (input <= 0)
return Status{RippledError::rpcINVALID_PARAMS, errMsg};
limit = clamp(static_cast<uint32_t>(input), lo, hi);
}
else
{
limit = def;
}
return {};
return handlerTable_.isClioOnly(method);
}
bool
shouldForwardToRippled(Context const& ctx)
RPCEngine::shouldForwardToRippled(Web::Context const& ctx) const
{
auto request = ctx.params;
@@ -310,16 +171,15 @@ shouldForwardToRippled(Context const& ctx)
}
Result
buildResponse(Context const& ctx)
RPCEngine::buildResponse(Web::Context const& ctx)
{
if (shouldForwardToRippled(ctx))
{
boost::json::object toForward = ctx.params;
auto toForward = ctx.params;
toForward["command"] = ctx.method;
auto res = ctx.balancer->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
ctx.counters.rpcForwarded(ctx.method);
auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
notifyForwarded(ctx.method);
if (!res)
return Status{RippledError::rpcFAILED_TO_FORWARD};
@@ -327,32 +187,30 @@ buildResponse(Context const& ctx)
return *res;
}
if (ctx.method == "ping")
return boost::json::object{};
if (ctx.backend->isTooBusy())
if (backend_->isTooBusy())
{
gLog.error() << "Database is too busy. Rejecting request";
return Status{RippledError::rpcTOO_BUSY};
}
auto method = handlerTable.getHandler(ctx.method);
auto const method = handlerTable_.getHandler(ctx.method);
if (!method)
return Status{RippledError::rpcUNKNOWN_COMMAND};
try
{
gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method << '`';
auto v = (*method)(ctx);
auto const isAdmin = ctx.clientIp == "127.0.0.1"; // TODO: this should be a strategy
auto const context = Context{ctx.yield, ctx.session, isAdmin, ctx.clientIp};
auto const v = (*method).process(ctx.params, context);
gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
if (auto object = get_if<boost::json::object>(&v); object && not shouldSuppressValidatedFlag(ctx))
{
(*object)[JS(validated)] = true;
}
return v;
if (v)
return v->as_object();
else
return Status{v.error()};
}
catch (InvalidParamsError const& err)
{
@@ -374,4 +232,25 @@ buildResponse(Context const& ctx)
}
}
void
RPCEngine::notifyComplete(std::string const& method, std::chrono::microseconds const& duration)
{
if (validHandler(method))
counters_.get().rpcComplete(method, duration);
}
void
RPCEngine::notifyErrored(std::string const& method)
{
if (validHandler(method))
counters_.get().rpcErrored(method);
}
void
RPCEngine::notifyForwarded(std::string const& method)
{
if (validHandler(method))
counters_.get().rpcForwarded(method);
}
} // namespace RPC

View File

@@ -20,16 +20,23 @@
#pragma once
#include <backend/BackendInterface.h>
#include <config/Config.h>
#include <log/Logger.h>
#include <rpc/Counters.h>
#include <rpc/Errors.h>
#include <rpc/HandlerTable.h>
#include <rpc/common/AnyHandler.h>
#include <util/Taggable.h>
#include <webserver/Context.h>
#include <webserver/DOSGuard.h>
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
#include <fmt/core.h>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
/*
@@ -50,41 +57,6 @@ class ReportingETL;
namespace RPC {
struct Context : public util::Taggable
{
clio::Logger perfLog_{"Performance"};
boost::asio::yield_context& yield;
std::string method;
std::uint32_t version;
boost::json::object const& params;
std::shared_ptr<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
{
ripple::uint256 index;
@@ -105,61 +77,131 @@ struct AccountCursor
using Result = std::variant<Status, boost::json::object>;
std::optional<Context>
std::optional<Web::Context>
make_WsContext(
boost::asio::yield_context& yc,
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,
util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range,
Counters& counters,
std::string const& clientIp);
std::optional<Context>
std::optional<Web::Context>
make_HttpContext(
boost::asio::yield_context& yc,
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,
Backend::LedgerRange const& range,
Counters& counters,
std::string const& clientIp);
Result
buildResponse(Context const& ctx);
/**
* @brief The RPC engine that ties all RPC-related functionality together
*/
class RPCEngine
{
std::shared_ptr<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
validHandler(std::string const& method);
HandlerTable handlerTable_;
bool
isClioOnly(std::string const& method);
public:
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
getLimit(RPC::Context const& context, std::uint32_t& limit);
static std::shared_ptr<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);
/**
* @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>
void
logDuration(Context const& ctx, T const& dur)
logDuration(Web::Context const& ctx, T const& dur)
{
using boost::json::serialize;
static clio::Logger log{"RPC"};
std::stringstream ss;
ss << ctx.tag()
<< "Request processing duration = " << std::chrono::duration_cast<std::chrono::milliseconds>(dur).count()
<< " milliseconds. request = " << ctx.params;
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
auto const msg =
fmt::format("Request processing duration = {} milliseconds. request = {}", millis, serialize(ctx.params));
if (seconds > 10)
log.error() << ss.str();
log.error() << ctx.tag() << msg;
else if (seconds > 1)
log.warn() << ss.str();
log.warn() << ctx.tag() << msg;
else
log.info() << ss.str();
log.info() << ctx.tag() << msg;
}
} // namespace RPC

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@
#pragma once
#include <config/Config.h>
#include <log/Logger.h>
#include <boost/asio.hpp>
@@ -41,9 +42,25 @@ class WorkQueue
uint32_t maxSize_ = std::numeric_limits<uint32_t>::max();
clio::Logger log_{"RPC"};
std::vector<std::thread> threads_ = {};
boost::asio::io_context ioc_ = {};
std::optional<boost::asio::io_context::work> work_{ioc_};
public:
WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0);
static WorkQueue
make_WorkQueue(clio::Config const& config)
{
static clio::Logger log{"RPC"};
auto const serverConfig = config.section("server");
auto const numThreads = config.valueOr<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>
bool
postCoro(F&& f, bool isWhiteListed)
@@ -53,38 +70,39 @@ public:
log_.warn() << "Queue is full. rejecting job. current size = " << curSize_ << " max size = " << maxSize_;
return false;
}
++curSize_;
auto start = std::chrono::system_clock::now();
// Each time we enqueue a job, we want to post a symmetrical job that
// will dequeue and run the job at the front of the job queue.
boost::asio::spawn(ioc_, [this, f = std::move(f), start](boost::asio::yield_context yield) {
auto run = std::chrono::system_clock::now();
auto wait = std::chrono::duration_cast<std::chrono::microseconds>(run - start).count();
// increment queued_ here, in the same place we implement
// durationUs_
// Each time we enqueue a job, we want to post a symmetrical job that will dequeue and run the job at the front
// of the job queue.
boost::asio::spawn(ioc_, [this, f = std::move(f), start](auto yield) {
auto const run = std::chrono::system_clock::now();
auto const wait = std::chrono::duration_cast<std::chrono::microseconds>(run - start).count();
// increment queued_ here, in the same place we implement durationUs_
++queued_;
durationUs_ += wait;
log_.info() << "WorkQueue wait time = " << wait << " queue size = " << curSize_;
f(yield);
--curSize_;
});
return true;
}
boost::json::object
report() const
{
boost::json::object obj;
auto obj = boost::json::object{};
obj["queued"] = queued_;
obj["queued_duration_us"] = durationUs_;
obj["current_queue_size"] = curSize_;
obj["max_queue_size"] = maxSize_;
return obj;
}
private:
std::vector<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/impl/Processors.h>
namespace RPCng {
namespace RPC {
/**
* @brief A type-erased Handler that can contain any (NextGen) RPC handler class
@@ -39,10 +39,8 @@ public:
* @brief Type-erases any handler class.
*
* @tparam HandlerType The real type of wrapped handler class
* @tparam ProcessingStrategy A strategy that implements how processing of
* JSON is to be done
* @param handler The handler to wrap. Required to fulfil the @ref Handler
* concept.
* @tparam ProcessingStrategy A strategy that implements how processing of JSON is to be done
* @param handler The handler to wrap. Required to fulfil the @ref Handler concept.
*/
template <Handler HandlerType, typename ProcessingStrategy = detail::DefaultProcessor<HandlerType>>
/* implicit */ AnyHandler(HandlerType&& handler)
@@ -54,6 +52,7 @@ public:
AnyHandler(AnyHandler const& other) : pimpl_{other.pimpl_->clone()}
{
}
AnyHandler&
operator=(AnyHandler const& rhs)
{
@@ -61,6 +60,7 @@ public:
pimpl_.swap(copy.pimpl_);
return *this;
}
AnyHandler(AnyHandler&&) = default;
AnyHandler&
operator=(AnyHandler&&) = default;
@@ -69,7 +69,7 @@ public:
* @brief Process incoming JSON by the stored handler
*
* @param value The JSON to process
* @return JSON result or @ref RPC::Status on error
* @return JSON result or @ref Status on error
*/
[[nodiscard]] ReturnType
process(boost::json::value const& value) const
@@ -78,11 +78,10 @@ public:
}
/**
* @brief Process incoming JSON by the stored handler in a provided
* coroutine
* @brief Process incoming JSON by the stored handler in a provided coroutine
*
* @param value The JSON to process
* @return JSON result or @ref RPC::Status on error
* @return JSON result or @ref Status on error
*/
[[nodiscard]] ReturnType
process(boost::json::value const& value, Context const& ctx) const
@@ -138,4 +137,4 @@ private:
std::unique_ptr<Concept> pimpl_;
};
} // namespace RPCng
} // namespace RPC

View File

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

View File

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

View File

@@ -26,7 +26,7 @@
#include <string>
#include <vector>
namespace RPCng {
namespace RPC {
/**
* @brief Represents a Specification for one field of an RPC command
@@ -51,7 +51,7 @@ struct FieldSpec final
* @brief Validates the passed JSON value using the stored requirements
*
* @param value The JSON value to validate
* @return Nothing on success; @ref RPC::Status on error
* @return Nothing on success; @ref Status on error
*/
[[nodiscard]] MaybeError
validate(boost::json::value const& value) const;
@@ -81,7 +81,7 @@ struct RpcSpec final
* @brief Validates the passed JSON value using the stored field specs
*
* @param value The JSON value to validate
* @return Nothing on success; @ref RPC::Status on error
* @return Nothing on success; @ref Status on error
*/
[[nodiscard]] MaybeError
validate(boost::json::value const& value) const;
@@ -90,4 +90,4 @@ private:
std::vector<FieldSpec> fields_;
};
} // namespace RPCng
} // namespace RPC

View File

@@ -27,29 +27,30 @@
class WsBase;
class SubscriptionManager;
namespace RPCng {
namespace RPC {
/**
* @brief Return type used for Validators that can return error but don't have
* specific value to return
*/
using MaybeError = util::Expected<void, RPC::Status>;
using MaybeError = util::Expected<void, Status>;
/**
* @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
*/
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
*/
using ReturnType = util::Expected<boost::json::value, RPC::Status>;
using ReturnType = util::Expected<boost::json::value, Status>;
struct RpcSpec;
struct FieldSpec;
@@ -70,10 +71,27 @@ struct Context
std::string clientIp;
};
class AnyHandler;
class HandlerProvider
{
public:
virtual ~HandlerProvider() = default;
virtual bool
contains(std::string const& method) const = 0;
virtual std::optional<AnyHandler>
getHandler(std::string const& command) const = 0;
virtual bool
isClioOnly(std::string const& command) const = 0;
};
inline void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, VoidOutput const&)
{
jv = boost::json::object{};
}
} // namespace RPCng
} // namespace RPC

View File

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

View File

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

View File

@@ -26,14 +26,14 @@
#include <optional>
namespace RPCng::detail {
namespace RPC::detail {
template <Requirement... Requirements>
[[nodiscard]] auto
makeFieldValidator(std::string const& key, Requirements&&... requirements)
{
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
// 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/Types.h>
namespace RPCng::detail {
namespace RPC::detail {
template <typename>
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,118 +17,170 @@
*/
//==============================================================================
#include <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/handlers/AccountChannels.h>
namespace RPC {
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;
jDst[JS(channel_id)] = ripple::to_string(line.key());
jDst[JS(account)] = ripple::to_string(line.getAccountID(ripple::sfAccount));
jDst[JS(destination_account)] = ripple::to_string(line.getAccountID(ripple::sfDestination));
jDst[JS(amount)] = line[ripple::sfAmount].getText();
jDst[JS(balance)] = line[ripple::sfBalance].getText();
if (publicKeyType(line[ripple::sfPublicKey]))
{
ripple::PublicKey const pk(line[ripple::sfPublicKey]);
jDst[JS(public_key)] = toBase58(ripple::TokenType::AccountPublic, pk);
jDst[JS(public_key_hex)] = strHex(pk);
}
jDst[JS(settle_delay)] = line[ripple::sfSettleDelay];
if (auto const& v = line[~ripple::sfExpiration])
jDst[JS(expiration)] = *v;
if (auto const& v = line[~ripple::sfCancelAfter])
jDst[JS(cancel_after)] = *v;
if (auto const& v = line[~ripple::sfSourceTag])
jDst[JS(source_tag)] = *v;
if (auto const& v = line[~ripple::sfDestinationTag])
jDst[JS(destination_tag)] = *v;
ChannelResponse channel;
channel.channelID = ripple::to_string(channelSle.key());
channel.account = ripple::to_string(channelSle.getAccountID(ripple::sfAccount));
channel.accountDestination = ripple::to_string(channelSle.getAccountID(ripple::sfDestination));
channel.amount = channelSle[ripple::sfAmount].getText();
channel.balance = channelSle[ripple::sfBalance].getText();
channel.settleDelay = channelSle[ripple::sfSettleDelay];
jsonLines.push_back(jDst);
if (publicKeyType(channelSle[ripple::sfPublicKey]))
{
ripple::PublicKey const pk(channelSle[ripple::sfPublicKey]);
channel.publicKey = toBase58(ripple::TokenType::AccountPublic, pk);
channel.publicKeyHex = strHex(pk);
}
if (auto const& v = channelSle[~ripple::sfExpiration])
channel.expiration = *v;
if (auto const& v = channelSle[~ripple::sfCancelAfter])
channel.cancelAfter = *v;
if (auto const& v = channelSle[~ripple::sfSourceTag])
channel.sourceTag = *v;
if (auto const& v = channelSle[~ripple::sfDestinationTag])
channel.destinationTag = *v;
jsonChannels.push_back(channel);
}
Result
doAccountChannels(Context const& context)
AccountChannelsHandler::Result
AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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 (auto const status = getAccount(request, accountID); status)
return status;
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto rawAcct =
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
ripple::AccountID destAccount;
if (auto const status = getAccount(request, destAccount, JS(destination_account)); status)
return status;
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
std::optional<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();
auto const destAccountID = input.destinationAccount ? 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 &&
(!destAccount || destAccount == sle.getAccountID(ripple::sfDestination)))
(!destAccountID || *destAccountID == sle.getAccountID(ripple::sfDestination)))
{
addChannel(jsonChannels, sle);
addChannel(response.channels, sle);
}
return true;
};
auto next =
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse);
auto const next = ngTraverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
if (auto status = std::get_if<RPC::Status>(&next))
return *status;
auto nextMarker = std::get<RPC::AccountCursor>(next);
response.account = input.account;
response.limit = input.limit;
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
auto const nextMarker = std::get<AccountCursor>(next);
if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString();
response.marker = nextMarker.toString();
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

View File

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

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,90 +17,99 @@
*/
//==============================================================================
#include <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 <rpc/RPCHelpers.h>
#include <rpc/handlers/AccountCurrencies.h>
namespace RPC {
Result
doAccountCurrencies(Context const& context)
AccountCurrenciesHandler::Result
AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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;
if (auto const status = getAccount(request, accountID); status)
return status;
auto const accountLedgerObject =
sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield);
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto rawAcct =
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::set<std::string> send, receive;
Output response;
auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE)
{
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
auto balance = sle.getFieldAmount(ripple::sfBalance);
auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit);
bool const viewLowest = (lowLimit.getIssuer() == accountID);
auto const lineLimit = viewLowest ? lowLimit : highLimit;
auto const lineLimitPeer = !viewLowest ? lowLimit : highLimit;
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
bool viewLowest = (lowLimit.getIssuer() == accountID);
auto lineLimit = viewLowest ? lowLimit : highLimit;
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
if (!viewLowest)
balance.negate();
if (balance < lineLimit)
receive.insert(ripple::to_string(balance.getCurrency()));
response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency()));
if ((-balance) < lineLimitPeer)
send.insert(ripple::to_string(balance.getCurrency()));
response.sendCurrencies.insert(ripple::to_string(balance.getCurrency()));
}
return true;
};
traverseOwnedNodes(
*context.backend,
accountID,
// traverse all owned nodes, limit->max, marker->empty
ngTraverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
context.yield,
ctx.yield,
addToResponse);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
response[JS(receive_currencies)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonReceive = response.at(JS(receive_currencies)).as_array();
for (auto const& currency : receive)
jsonReceive.push_back(currency.c_str());
response[JS(send_currencies)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonSend = response.at(JS(send_currencies)).as_array();
for (auto const& currency : send)
jsonSend.push_back(currency.c_str());
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
return response;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountCurrenciesHandler::Output const& output)
{
jv = {
{JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
{JS(receive_currencies), output.receiveCurrencies},
{JS(send_currencies), output.sendCurrencies},
};
}
AccountCurrenciesHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

@@ -26,7 +26,7 @@
#include <set>
namespace RPCng {
namespace RPC {
class AccountCurrenciesHandler
{
// dependencies
@@ -43,7 +43,7 @@ public:
bool validated = true;
};
// TODO:we did not implement the "strict" field
// TODO: We did not implement the "strict" field (can't be implemented?)
struct Input
{
std::string account;
@@ -51,7 +51,7 @@ public:
std::optional<uint32_t> ledgerIndex;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
AccountCurrenciesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -80,4 +80,5 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,98 +17,110 @@
*/
//==============================================================================
#include <ripple/protocol/Indexes.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.
// }
#include <rpc/handlers/AccountInfo.h>
namespace RPC {
Result
doAccountInfo(Context const& context)
AccountInfoHandler::Result
AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
if (!input.account && !input.ident)
return Error{Status{RippledError::rpcACT_MALFORMED}};
std::string strIdent;
if (request.contains(JS(account)))
strIdent = request.at(JS(account)).as_string().c_str();
else if (request.contains(JS(ident)))
strIdent = request.at(JS(ident)).as_string().c_str();
else
return Status{RippledError::rpcACT_MALFORMED};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
// We only need to fetch the ledger header because the ledger hash is
// supposed to be included in the response. The ledger sequence is specified
// in the request
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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.
auto accountID = accountFromStringStrict(strIdent);
if (!accountID)
return Status{RippledError::rpcACT_MALFORMED};
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto key = ripple::keylet::account(accountID.value());
std::optional<std::vector<unsigned char>> dbResponse =
context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield);
ripple::STLedgerEntry const sle{
ripple::SerialIter{accountLedgerObject->data(), accountLedgerObject->size()}, accountKeylet.key};
if (!dbResponse)
return Status{RippledError::rpcACT_NOT_FOUND};
ripple::STLedgerEntry sle{ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
if (!key.check(sle))
return Status{RippledError::rpcDB_DESERIALIZATION};
response[JS(account_data)] = toJson(sle);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
if (!accountKeylet.check(sle))
return Error{Status{RippledError::rpcDB_DESERIALIZATION}};
// Return SignerList(s) if that is requested.
if (request.contains(JS(signer_lists)) && request.at(JS(signer_lists)).as_bool())
if (input.signerLists)
{
// We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account.
boost::json::array signerList;
auto signersKey = ripple::keylet::signers(*accountID);
auto const signersKey = ripple::keylet::signers(*accountID);
// This code will need to be revisited if in the future we
// support multiple SignerLists on one account.
auto const signers = context.backend->fetchLedgerObject(signersKey.key, lgrInfo.seq, context.yield);
auto const signers = sharedPtrBackend_->fetchLedgerObject(signersKey.key, lgrInfo.seq, ctx.yield);
std::vector<ripple::STLedgerEntry> signerList;
if (signers)
{
ripple::STLedgerEntry sleSigners{ripple::SerialIter{signers->data(), signers->size()}, signersKey.key};
if (!signersKey.check(sleSigners))
return Status{RippledError::rpcDB_DESERIALIZATION};
ripple::STLedgerEntry const sleSigners{
ripple::SerialIter{signers->data(), signers->size()}, signersKey.key};
signerList.push_back(toJson(sleSigners));
if (!signersKey.check(sleSigners))
return Error{Status{RippledError::rpcDB_DESERIALIZATION}};
signerList.push_back(sleSigners);
}
response[JS(account_data)].as_object()[JS(signer_lists)] = std::move(signerList);
return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, signerList);
}
return response;
return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle);
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandler::Output const& output)
{
jv = boost::json::object{
{JS(account_data), toJson(output.accountData)},
{JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex},
};
if (output.signerLists)
{
jv.as_object()[JS(signer_lists)] = boost::json::array();
for (auto const& signerList : output.signerLists.value())
jv.as_object()[JS(signer_lists)].as_array().push_back(toJson(signerList));
}
}
AccountInfoHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class AccountInfoHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -68,7 +68,7 @@ public:
bool signerLists = false;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
AccountInfoHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -98,4 +98,5 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,146 +17,107 @@
*/
//==============================================================================
#include <ripple/app/ledger/Ledger.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 <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <rpc/RPCHelpers.h>
#include <rpc/RPC.h>
#include <rpc/handlers/AccountLines.h>
namespace RPC {
void
addLine(
boost::json::array& jsonLines,
ripple::SLE const& line,
AccountLinesHandler::addLine(
std::vector<LineResponse>& lines,
ripple::SLE const& lineSle,
ripple::AccountID const& account,
std::optional<ripple::AccountID> const& peerAccount)
std::optional<ripple::AccountID> const& peerAccount) const
{
auto flags = line.getFieldU32(ripple::sfFlags);
auto lowLimit = line.getFieldAmount(ripple::sfLowLimit);
auto highLimit = line.getFieldAmount(ripple::sfHighLimit);
auto lowID = lowLimit.getIssuer();
auto highID = highLimit.getIssuer();
auto lowQualityIn = line.getFieldU32(ripple::sfLowQualityIn);
auto lowQualityOut = line.getFieldU32(ripple::sfLowQualityOut);
auto highQualityIn = line.getFieldU32(ripple::sfHighQualityIn);
auto highQualityOut = line.getFieldU32(ripple::sfHighQualityOut);
auto balance = line.getFieldAmount(ripple::sfBalance);
auto const flags = lineSle.getFieldU32(ripple::sfFlags);
auto const lowLimit = lineSle.getFieldAmount(ripple::sfLowLimit);
auto const highLimit = lineSle.getFieldAmount(ripple::sfHighLimit);
auto const lowID = lowLimit.getIssuer();
auto const highID = highLimit.getIssuer();
auto const lowQualityIn = lineSle.getFieldU32(ripple::sfLowQualityIn);
auto const lowQualityOut = lineSle.getFieldU32(ripple::sfLowQualityOut);
auto const highQualityIn = lineSle.getFieldU32(ripple::sfHighQualityIn);
auto const highQualityOut = lineSle.getFieldU32(ripple::sfHighQualityOut);
auto balance = lineSle.getFieldAmount(ripple::sfBalance);
bool viewLowest = (lowID == account);
auto lineLimit = viewLowest ? lowLimit : highLimit;
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
auto lineAccountIDPeer = !viewLowest ? lowID : highID;
auto lineQualityIn = viewLowest ? lowQualityIn : highQualityIn;
auto lineQualityOut = viewLowest ? lowQualityOut : highQualityOut;
auto const viewLowest = (lowID == account);
auto const lineLimit = viewLowest ? lowLimit : highLimit;
auto const lineLimitPeer = not viewLowest ? lowLimit : highLimit;
auto const lineAccountIDPeer = not viewLowest ? lowID : highID;
auto const lineQualityIn = viewLowest ? lowQualityIn : highQualityIn;
auto const lineQualityOut = viewLowest ? lowQualityOut : highQualityOut;
if (peerAccount && peerAccount != lineAccountIDPeer)
return;
if (!viewLowest)
if (not viewLowest)
balance.negate();
bool lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool lineAuthPeer = flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool lineNoRipplePeer = flags & (!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
bool lineFreezePeer = flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
bool const lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool const lineAuthPeer = flags & (not viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
bool const lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool const lineNoRipplePeer = flags & (not viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
bool const lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
bool const lineFreezePeer = flags & (not viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
ripple::STAmount const& saBalance(balance);
ripple::STAmount const& saLimit(lineLimit);
ripple::STAmount const& saLimitPeer(lineLimitPeer);
ripple::STAmount const& saBalance = balance;
ripple::STAmount const& saLimit = lineLimit;
ripple::STAmount const& saLimitPeer = lineLimitPeer;
LineResponse line;
line.account = ripple::to_string(lineAccountIDPeer);
line.balance = saBalance.getText();
line.currency = ripple::to_string(saBalance.issue().currency);
line.limit = saLimit.getText();
line.limitPeer = saLimitPeer.getText();
line.qualityIn = lineQualityIn;
line.qualityOut = lineQualityOut;
boost::json::object jPeer;
jPeer[JS(account)] = ripple::to_string(lineAccountIDPeer);
jPeer[JS(balance)] = saBalance.getText();
jPeer[JS(currency)] = ripple::to_string(saBalance.issue().currency);
jPeer[JS(limit)] = saLimit.getText();
jPeer[JS(limit_peer)] = saLimitPeer.getText();
jPeer[JS(quality_in)] = lineQualityIn;
jPeer[JS(quality_out)] = lineQualityOut;
if (lineAuth)
jPeer[JS(authorized)] = true;
if (lineAuthPeer)
jPeer[JS(peer_authorized)] = true;
if (lineFreeze)
jPeer[JS(freeze)] = true;
if (lineFreezePeer)
jPeer[JS(freeze_peer)] = true;
jPeer[JS(no_ripple)] = lineNoRipple;
jPeer[JS(no_ripple_peer)] = lineNoRipplePeer;
line.authorized = true;
jsonLines.push_back(jPeer);
if (lineAuthPeer)
line.peerAuthorized = true;
if (lineFreeze)
line.freeze = true;
if (lineFreezePeer)
line.freezePeer = true;
line.noRipple = lineNoRipple;
line.noRipplePeer = lineNoRipplePeer;
lines.push_back(line);
}
Result
doAccountLines(Context const& context)
AccountLinesHandler::Result
AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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 (auto const status = getAccount(request, accountID); status)
return status;
if (not accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto rawAcct =
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
auto const peerAccountID = input.peer ? accountFromStringStrict(*(input.peer)) : std::optional<ripple::AccountID>{};
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
Output response;
response.lines.reserve(input.limit);
std::optional<ripple::AccountID> peerAccount;
if (auto const status = getOptionalAccount(request, peerAccount, JS(peer)); status)
return status;
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
std::optional<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 {
auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE)
{
auto ignore = false;
if (ignoreDefault)
if (input.ignoreDefault)
{
if (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() == accountID)
ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfLowReserve);
@@ -164,23 +125,109 @@ doAccountLines(Context const& context)
ignore = !(sle.getFieldU32(ripple::sfFlags) & ripple::lsfHighReserve);
}
if (!ignore)
addLine(jsonLines, sle, accountID, peerAccount);
if (not ignore)
addLine(response.lines, sle, *accountID, peerAccountID);
}
};
auto next =
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse);
auto const next = ngTraverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
auto const nextMarker = std::get<AccountCursor>(next);
if (auto status = std::get_if<RPC::Status>(&next))
return *status;
auto nextMarker = std::get<RPC::AccountCursor>(next);
response.account = input.account;
response.limit = input.limit; // not documented,
// https://github.com/XRPLF/xrpl-dev-portal/issues/1838
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString();
response.marker = nextMarker.toString();
return response;
}
AccountLinesHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class AccountNFTsHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -51,7 +51,7 @@ public:
std::optional<std::string> marker;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
AccountNFTsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -81,4 +81,5 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,25 +17,12 @@
*/
//==============================================================================
#include <ripple/app/ledger/Ledger.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>
#include <rpc/handlers/AccountObjects.h>
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},
{"ticket", ripple::ltTICKET},
{"signer_list", ripple::ltSIGNER_LIST},
@@ -45,170 +32,102 @@ std::unordered_map<std::string, ripple::LedgerEntryType> types{
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
{"check", ripple::ltCHECK},
{"nft_page", ripple::ltNFTOKEN_PAGE},
{"nft_offer", ripple::ltNFTOKEN_OFFER}};
{"nft_offer", ripple::ltNFTOKEN_OFFER},
};
Result
doAccountNFTs(Context const& context)
AccountObjectsHandler::Result
AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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 (auto const status = getAccount(request, accountID); status)
return status;
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
if (!accountID)
return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"};
Output response;
auto const addToResponse = [&](ripple::SLE&& sle) {
if (!input.type || sle.getType() == *(input.type))
response.accountObjects.push_back(std::move(sle));
return true;
};
auto rawAcct =
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
auto const next = ngTraverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
if (auto status = std::get_if<Status>(&next))
return Error{*status};
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
response.limit = input.limit;
response.account = input.account;
ripple::uint256 marker;
if (auto const status = getHexMarker(request, marker); status)
return status;
auto const& nextMarker = std::get<AccountCursor>(next);
response[JS(account)] = ripple::toBase58(accountID);
response[JS(validated)] = true;
response[JS(limit)] = limit;
std::uint32_t numPages = 0;
response[JS(account_nfts)] = boost::json::value(boost::json::array_kind);
auto& nfts = response.at(JS(account_nfts)).as_array();
// if a marker was passed, start at the page specified in marker. Else,
// start at the max page
auto const pageKey = marker.isZero() ? ripple::keylet::nftpage_max(accountID).key : marker;
auto const blob = context.backend->fetchLedgerObject(pageKey, lgrInfo.seq, context.yield);
if (!blob)
return response;
std::optional<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();
}
if (nextMarker.isNonZero())
response.marker = nextMarker.toString();
return response;
}
Result
doAccountObjects(Context const& context)
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHandler::Output const& output)
{
auto request = context.params;
boost::json::object response = {};
auto objects = boost::json::array{};
for (auto const& sle : output.accountObjects)
objects.push_back(toJson(sle));
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::AccountID accountID;
if (auto const status = getAccount(request, accountID); status)
return status;
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
std::optional<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));
}
jv = {
{JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
{JS(limit), output.limit},
{JS(account), output.account},
{JS(account_objects), objects},
};
auto next =
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse);
if (output.marker)
jv.as_object()[JS(marker)] = *(output.marker);
}
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
AccountObjectsHandler::Input
tag_invoke(boost::json::value_to_tag<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))
return *status;
input.account = jv.at(JS(account)).as_string().c_str();
auto const& nextMarker = std::get<RPC::AccountCursor>(next);
if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str();
return response;
if (jsonObject.contains(JS(ledger_index)))
{
if (!jsonObject.at(JS(ledger_index)).is_string())
input.ledgerIndex = jv.at(JS(ledger_index)).as_int64();
else if (jsonObject.at(JS(ledger_index)).as_string() != "validated")
input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str());
}
if (jsonObject.contains(JS(type)))
input.type = AccountObjectsHandler::TYPESMAP.at(jv.at(JS(type)).as_string().c_str());
if (jsonObject.contains(JS(limit)))
input.limit = jv.at(JS(limit)).as_int64();
if (jsonObject.contains(JS(marker)))
input.marker = jv.at(JS(marker)).as_string().c_str();
return input;
}
} // namespace RPC

View File

@@ -26,11 +26,13 @@
#include <set>
namespace RPCng {
namespace RPC {
class AccountObjectsHandler
{
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
// constants
static std::unordered_map<std::string, ripple::LedgerEntryType> const TYPESMAP;
public:
@@ -51,12 +53,12 @@ public:
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = 200; //[10,400]
uint32_t limit = 200; // [10,400]
std::optional<std::string> marker;
std::optional<ripple::LedgerEntryType> type;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
AccountObjectsHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -83,7 +85,8 @@ public:
"deposit_preauth",
"check",
"nft_page",
"nft_offer"}},
"nft_offer",
}},
{JS(marker), validation::AccountMarkerValidator},
};
@@ -100,4 +103,5 @@ private:
friend Input
tag_invoke(boost::json::value_to_tag<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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,133 +17,144 @@
*/
//==============================================================================
#include <ripple/app/ledger/Ledger.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>
#include <rpc/handlers/AccountOffers.h>
namespace RPC {
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));
ripple::STAmount rate = ripple::amountFromQuality(quality);
auto offer = AccountOffersHandler::Offer();
offer.takerPays = offerSle.getFieldAmount(ripple::sfTakerPays);
offer.takerGets = offerSle.getFieldAmount(ripple::sfTakerGets);
offer.seq = offerSle.getFieldU32(ripple::sfSequence);
offer.flags = offerSle.getFieldU32(ripple::sfFlags);
ripple::STAmount takerPays = offer.getFieldAmount(ripple::sfTakerPays);
ripple::STAmount takerGets = offer.getFieldAmount(ripple::sfTakerGets);
auto const quality = getQuality(offerSle.getFieldH256(ripple::sfBookDirectory));
auto const rate = ripple::amountFromQuality(quality);
offer.quality = rate.getText();
boost::json::object obj;
if (offerSle.isFieldPresent(ripple::sfExpiration))
offer.expiration = offerSle.getFieldU32(ripple::sfExpiration);
if (!takerPays.native())
{
obj[JS(taker_pays)] = boost::json::value(boost::json::object_kind);
boost::json::object& takerPaysJson = obj.at(JS(taker_pays)).as_object();
takerPaysJson[JS(value)] = takerPays.getText();
takerPaysJson[JS(currency)] = ripple::to_string(takerPays.getCurrency());
takerPaysJson[JS(issuer)] = ripple::to_string(takerPays.getIssuer());
}
else
{
obj[JS(taker_pays)] = takerPays.getText();
}
if (!takerGets.native())
{
obj[JS(taker_gets)] = boost::json::value(boost::json::object_kind);
boost::json::object& takerGetsJson = obj.at(JS(taker_gets)).as_object();
takerGetsJson[JS(value)] = takerGets.getText();
takerGetsJson[JS(currency)] = ripple::to_string(takerGets.getCurrency());
takerGetsJson[JS(issuer)] = ripple::to_string(takerGets.getIssuer());
}
else
{
obj[JS(taker_gets)] = takerGets.getText();
}
obj[JS(seq)] = offer.getFieldU32(ripple::sfSequence);
obj[JS(flags)] = offer.getFieldU32(ripple::sfFlags);
obj[JS(quality)] = rate.getText();
if (offer.isFieldPresent(ripple::sfExpiration))
obj[JS(expiration)] = offer.getFieldU32(ripple::sfExpiration);
offersJson.push_back(obj);
offers.push_back(offer);
};
Result
doAccountOffers(Context const& context)
AccountOffersHandler::Result
AccountOffersHandler::process(AccountOffersHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*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 (auto const status = getAccount(request, accountID); status)
return status;
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
auto rawAcct =
context.backend->fetchLedgerObject(ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct)
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
std::optional<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();
Output response;
response.account = ripple::to_string(*accountID);
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltOFFER)
{
addOffer(jsonLines, sle);
}
addOffer(response.offers, sle);
return true;
};
auto next =
traverseOwnedNodes(*context.backend, accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse);
auto const next = ngTraverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (auto status = std::get_if<RPC::Status>(&next))
return *status;
if (auto const status = std::get_if<Status>(&next))
return Error{*status};
auto nextMarker = std::get<RPC::AccountCursor>(next);
auto const nextMarker = std::get<AccountCursor>(next);
if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString();
response.marker = nextMarker.toString();
return response;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Output const& output)
{
jv = {
{JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
{JS(account), output.account},
{JS(offers), boost::json::value_from(output.offers)},
};
if (output.marker)
jv.as_object()[JS(marker)] = *output.marker;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHandler::Offer const& offer)
{
jv = {
{JS(seq), offer.seq},
{JS(flags), offer.flags},
{JS(quality), offer.quality},
};
auto& jsonObject = jv.as_object();
if (offer.expiration)
jsonObject[JS(expiration)] = *offer.expiration;
auto const convertAmount = [&](const char* field, ripple::STAmount const& amount) {
if (amount.native())
jsonObject[field] = amount.getText();
else
jsonObject[field] = {
{JS(currency), ripple::to_string(amount.getCurrency())},
{JS(issuer), ripple::to_string(amount.getIssuer())},
{JS(value), amount.getText()},
};
};
convertAmount(JS(taker_pays), offer.takerPays);
convertAmount(JS(taker_gets), offer.takerGets);
}
AccountOffersHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

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

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,48 +17,196 @@
*/
//==============================================================================
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/AccountTx.h>
#include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
Result
doAccountTx(Context const& context)
// TODO: this is currently very similar to nft_history but its own copy for time
// being. we should aim to reuse common logic in some way in the future.
AccountTxHandler::Result
AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) const
{
ripple::AccountID accountID;
if (auto const status = getAccount(context.params, accountID); status)
return status;
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto [minIndex, maxIndex] = *range;
constexpr std::string_view outerFuncName = __func__;
auto const maybeResponse = traverseTransactions(
context,
[&accountID, &outerFuncName](
std::shared_ptr<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 (input.ledgerIndexMin)
{
if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin)
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
if (auto const status = std::get_if<Status>(&maybeResponse); status)
return *status;
auto response = std::get<boost::json::object>(maybeResponse);
minIndex = *input.ledgerIndexMin;
}
if (input.ledgerIndexMax)
{
if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax)
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}};
maxIndex = *input.ledgerIndexMax;
}
if (minIndex > maxIndex)
return Error{Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"}};
if (input.ledgerHash || input.ledgerIndex)
{
// rippled does not have this check
if (input.ledgerIndexMax || input.ledgerIndexMin)
return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (auto status = std::get_if<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;
}
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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class BookChangesHandler
{
@@ -48,7 +48,7 @@ public:
std::optional<uint32_t> ledgerIndex;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
BookChangesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -61,6 +61,7 @@ public:
{JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator},
};
return rpcSpec;
}
@@ -74,4 +75,5 @@ private:
friend Input
tag_invoke(boost::json::value_to_tag<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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,90 +17,85 @@
*/
//==============================================================================
#include <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 <boost/json.hpp>
#include <algorithm>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
#include <rpc/handlers/BookOffers.h>
namespace RPC {
Result
doBookOffers(Context const& context)
BookOffersHandler::Result
BookOffersHandler::process(Input input, Context const& ctx) const
{
auto request = context.params;
auto bookMaybe = parseBook(input.paysCurrency, input.paysID, input.getsCurrency, input.getsID);
if (auto const status = std::get_if<Status>(&bookMaybe))
return Error{*status};
boost::json::object response = {};
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
// check ledger
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
ripple::Book book;
ripple::uint256 bookBase;
if (request.contains("book"))
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);
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())
return Status{RippledError::rpcINVALID_PARAMS, "bookNotString"};
if (!bookBase.parseHex(request.at("book").as_string().c_str()))
return Status{RippledError::rpcINVALID_PARAMS, "invalidBook"};
}
else
{
auto parsed = parseBook(request);
if (auto status = std::get_if<Status>(&parsed))
return *status;
else
{
book = std::get<ripple::Book>(parsed);
bookBase = getBookBase(book);
}
if (!jsonObject.at(JS(ledger_index)).is_string())
input.ledgerIndex = jv.at(JS(ledger_index)).as_int64();
else if (jsonObject.at(JS(ledger_index)).as_string() != "validated")
input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str());
}
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
if (jsonObject.contains(JS(taker)))
input.taker = accountFromStringStrict(jv.at(JS(taker)).as_string().c_str());
ripple::AccountID takerID = beast::zero;
if (auto const status = getTaker(request, takerID); status)
return status;
if (jsonObject.contains(JS(limit)))
input.limit = jv.at(JS(limit)).as_int64();
auto start = std::chrono::system_clock::now();
auto [offers, _] = context.backend->fetchBookOffers(bookBase, lgrInfo.seq, limit, context.yield);
auto end = std::chrono::system_clock::now();
gLog.warn() << "Time loading books: " << std::chrono::duration_cast<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;
return input;
}
} // namespace RPC

View File

@@ -23,7 +23,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class BookOffersHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -37,8 +37,8 @@ public:
bool validated = true;
};
// the taker is not really used in both clio and rippled, both of them
// return all the offers regardless the funding status
// the taker is not really used in both clio and rippled, both of them return all the offers regardless the funding
// status
struct Input
{
std::optional<std::string> ledgerHash;
@@ -47,13 +47,12 @@ public:
std::optional<ripple::AccountID> taker;
ripple::Currency paysCurrency;
ripple::Currency getsCurrency;
// accountID will be filled by input converter, if no issuer is given,
// will use XRP issuer
// accountID will be filled by input converter, if no issuer is given, will use XRP issuer
ripple::AccountID paysID = ripple::xrpAccount();
ripple::AccountID getsID = ripple::xrpAccount();
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
BookOffersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -70,10 +69,10 @@ public:
{JS(currency),
validation::Required{},
validation::WithCustomError{
validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcDST_AMT_MALFORMED)}},
validation::CurrencyValidator, Status(RippledError::rpcDST_AMT_MALFORMED)}},
{JS(issuer),
validation::WithCustomError{
validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcDST_ISR_MALFORMED)}}}},
validation::IssuerValidator, Status(RippledError::rpcDST_ISR_MALFORMED)}}}},
{JS(taker_pays),
validation::Required{},
validation::Type<boost::json::object>{},
@@ -81,10 +80,10 @@ public:
{JS(currency),
validation::Required{},
validation::WithCustomError{
validation::CurrencyValidator, RPC::Status(RPC::RippledError::rpcSRC_CUR_MALFORMED)}},
validation::CurrencyValidator, Status(RippledError::rpcSRC_CUR_MALFORMED)}},
{JS(issuer),
validation::WithCustomError{
validation::IssuerValidator, RPC::Status(RPC::RippledError::rpcSRC_ISR_MALFORMED)}}}},
validation::IssuerValidator, Status(RippledError::rpcSRC_ISR_MALFORMED)}}}},
{JS(taker), validation::AccountValidator},
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}},
{JS(ledger_hash), validation::Uint256HexStringValidator},
@@ -104,4 +103,4 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,177 +17,138 @@
*/
//==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/GatewayBalances.h>
namespace RPC {
Result
doGatewayBalances(Context const& context)
GatewayBalancesHandler::Result
GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
// check ledger
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
ripple::AccountID accountID;
if (auto const status = getAccount(request, accountID); status)
return status;
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
// check account
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);
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!accountLedgerObject)
return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
std::map<ripple::Currency, ripple::STAmount> sums;
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;
auto output = GatewayBalancesHandler::Output{};
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) {
if (sle.getType() == ripple::ltRIPPLE_STATE)
{
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit);
auto const lowID = lowLimit.getIssuer();
auto const highID = highLimit.getIssuer();
auto const viewLowest = (lowLimit.getIssuer() == accountID);
auto const flags = sle.getFieldU32(ripple::sfFlags);
auto const freeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
auto lowID = lowLimit.getIssuer();
auto highID = highLimit.getIssuer();
bool viewLowest = (lowLimit.getIssuer() == accountID);
auto lineLimit = viewLowest ? lowLimit : highLimit;
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
auto flags = sle.getFieldU32(ripple::sfFlags);
auto freeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
if (!viewLowest)
balance.negate();
int balSign = balance.signum();
auto const balSign = balance.signum();
if (balSign == 0)
return true;
auto const& peer = !viewLowest ? lowID : highID;
// Here, a negative balance means the cold wallet owes (normal)
// A positive balance means the cold wallet has an asset
// (unusual)
// A positive balance means the cold wallet has an asset (unusual)
if (hotWallets.count(peer) > 0)
if (input.hotWallets.count(peer) > 0)
{
// This is a specified hot wallet
hotBalances[peer].push_back(-balance);
output.hotBalances[peer].push_back(-balance);
}
else if (balSign > 0)
{
// This is a gateway asset
assets[peer].push_back(balance);
output.assets[peer].push_back(balance);
}
else if (freeze)
{
// An obligation the gateway has frozen
frozenBalances[peer].push_back(-balance);
output.frozenBalances[peer].push_back(-balance);
}
else
{
// normal negative balance, obligation to customer
auto& bal = sums[balance.getCurrency()];
auto& bal = output.sums[balance.getCurrency()];
if (bal == beast::zero)
{
// This is needed to set the currency code correctly
bal = -balance;
}
else
{ // when overflow happens, insert a flag to indicate
// https://github.com/XRPLF/rippled/pull/4355
{
try
{
bal -= balance;
}
catch (std::runtime_error& e)
catch (std::runtime_error const& e)
{
response["overflow"] = true;
output.overflow = true;
}
}
}
}
return true;
};
auto result = traverseOwnedNodes(
*context.backend,
accountID,
// traverse all owned nodes, limit->max, marker->empty
auto const ret = ngTraverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
context.yield,
ctx.yield,
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;
for (auto const& [k, v] : sums)
{
obj[ripple::to_string(k)] = v.getText();
}
response[JS(obligations)] = std::move(obj);
boost::json::object obligations;
for (auto const& [k, v] : output.sums)
obligations[ripple::to_string(k)] = v.getText();
obj[JS(obligations)] = std::move(obligations);
}
auto toJson = [](std::map<ripple::AccountID, std::vector<ripple::STAmount>> const& balances) {
boost::json::object obj;
if (!balances.empty())
auto const toJson = [](std::map<ripple::AccountID, std::vector<ripple::STAmount>> const& balances) {
boost::json::object balancesObj;
if (not balances.empty())
{
for (auto const& [accId, accBalances] : balances)
{
@@ -199,25 +160,72 @@ doGatewayBalances(Context const& context)
entry[JS(value)] = balance.getText();
arr.push_back(std::move(entry));
}
obj[ripple::to_string(accId)] = std::move(arr);
balancesObj[ripple::to_string(accId)] = std::move(arr);
}
}
return obj;
return balancesObj;
};
auto containsHotWallet = [&](auto const& hw) { return hotBalances.contains(hw); };
if (not std::all_of(hotWallets.begin(), hotWallets.end(), containsHotWallet))
return Status{RippledError::rpcINVALID_PARAMS, "invalidHotWallet"};
if (auto balances = toJson(output.hotBalances); balances.size())
obj[JS(balances)] = balances;
if (auto balances = toJson(hotBalances); balances.size())
response[JS(balances)] = balances;
if (auto balances = toJson(frozenBalances); balances.size())
response[JS(frozen_balances)] = balances;
if (auto balances = toJson(assets); assets.size())
response[JS(assets)] = toJson(assets);
response[JS(account)] = request.at(JS(account));
response[JS(ledger_index)] = lgrInfo.seq;
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
return response;
// we don't have frozen_balances field in the
// document:https://xrpl.org/gateway_balances.html#gateway_balances
if (auto balances = toJson(output.frozenBalances); balances.size())
obj[JS(frozen_balances)] = balances;
if (auto balances = toJson(output.assets); balances.size())
obj[JS(assets)] = balances;
obj[JS(account)] = output.accountID;
obj[JS(ledger_index)] = output.ledgerIndex;
obj[JS(ledger_hash)] = output.ledgerHash;
if (output.overflow)
obj["overflow"] = true;
jv = std::move(obj);
}
GatewayBalancesHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class GatewayBalancesHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -53,7 +53,7 @@ public:
std::optional<uint32_t> ledgerIndex;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
GatewayBalancesHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -66,10 +66,8 @@ public:
static auto const hotWalletValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string() && !value.is_array())
{
return Error{
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}};
}
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotStringOrArray"}};
// wallet needs to be an valid accountID or public key
auto const wallets = value.is_array() ? value.as_array() : boost::json::array{value};
auto const getAccountID = [](auto const& j) -> std::optional<ripple::AccountID> {
@@ -77,17 +75,22 @@ public:
{
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 {};
};
for (auto const& wallet : wallets)
{
if (!getAccountID(wallet))
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}};
}
return MaybeError{};
}};
@@ -111,4 +114,5 @@ private:
friend Input
tag_invoke(boost::json::value_to_tag<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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,160 +17,166 @@
*/
//==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/Ledger.h>
namespace RPC {
Result
doLedger(Context const& context)
LedgerHandler::Result
LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const
{
auto params = context.params;
boost::json::object response = {};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
bool binary = false;
if (params.contains(JS(binary)))
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
Output output;
if (input.binary)
{
if (!params.at(JS(binary)).is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = params.at(JS(binary)).as_bool();
}
bool transactions = false;
if (params.contains(JS(transactions)))
{
if (!params.at(JS(transactions)).is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "transactionsFlagNotBool"};
transactions = params.at(JS(transactions)).as_bool();
}
bool expand = false;
if (params.contains(JS(expand)))
{
if (!params.at(JS(expand)).is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "expandFlagNotBool"};
expand = params.at(JS(expand)).as_bool();
}
bool diff = false;
if (params.contains("diff"))
{
if (!params.at("diff").is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "diffFlagNotBool"};
diff = params.at("diff").as_bool();
}
if (params.contains(JS(full)))
return Status{RippledError::rpcNOT_SUPPORTED};
if (params.contains(JS(accounts)))
return Status{RippledError::rpcNOT_SUPPORTED};
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<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));
output.header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
}
else
{
header[JS(accepted)] = true;
header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
header[JS(close_flags)] = lgrInfo.closeFlags;
header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count();
header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count();
header[JS(closed)] = true;
header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count();
header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash);
header[JS(seqNum)] = std::to_string(lgrInfo.seq);
header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops);
header[JS(total_coins)] = ripple::to_string(lgrInfo.drops);
header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash);
output.header[JS(accepted)] = true;
output.header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
output.header[JS(close_flags)] = lgrInfo.closeFlags;
output.header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count();
output.header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
output.header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count();
output.header[JS(closed)] = true;
output.header[JS(hash)] = ripple::strHex(lgrInfo.hash);
output.header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
output.header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
output.header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count();
output.header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash);
output.header[JS(seqNum)] = std::to_string(lgrInfo.seq);
output.header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops);
output.header[JS(total_coins)] = ripple::to_string(lgrInfo.drops);
output.header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash);
}
header[JS(closed)] = true;
if (transactions)
output.header[JS(closed)] = true;
if (input.transactions)
{
header[JS(transactions)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonTxs = header.at(JS(transactions)).as_array();
if (expand)
output.header[JS(transactions)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonTxs = output.header.at(JS(transactions)).as_array();
if (input.expand)
{
auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq, context.yield);
auto txns = sharedPtrBackend_->fetchAllTransactionsInLedger(lgrInfo.seq, ctx.yield);
std::transform(
std::move_iterator(txns.begin()),
std::move_iterator(txns.end()),
std::back_inserter(jsonTxs),
[binary](auto obj) {
[&](auto obj) {
boost::json::object entry;
if (!binary)
if (!input.binary)
{
auto [txn, meta] = toExpandedJson(obj);
entry = txn;
entry[JS(metaData)] = meta;
entry = std::move(txn);
entry[JS(metaData)] = std::move(meta);
}
else
{
entry[JS(tx_blob)] = ripple::strHex(obj.transaction);
entry[JS(meta)] = ripple::strHex(obj.metadata);
}
// entry[JS(ledger_index)] = obj.ledgerSequence;
return entry;
});
}
else
{
auto hashes = context.backend->fetchAllTransactionHashesInLedger(lgrInfo.seq, context.yield);
auto hashes = sharedPtrBackend_->fetchAllTransactionHashesInLedger(lgrInfo.seq, ctx.yield);
std::transform(
std::move_iterator(hashes.begin()),
std::move_iterator(hashes.end()),
std::back_inserter(jsonTxs),
[](auto hash) {
boost::json::object entry;
return boost::json::string(ripple::strHex(hash));
});
[](auto hash) { return boost::json::string(ripple::strHex(hash)); });
}
}
if (diff)
if (input.diff)
{
header["diff"] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonDiff = header.at("diff").as_array();
auto diff = context.backend->fetchLedgerDiff(lgrInfo.seq, context.yield);
output.header["diff"] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonDiff = output.header.at("diff").as_array();
auto diff = sharedPtrBackend_->fetchLedgerDiff(lgrInfo.seq, ctx.yield);
for (auto const& obj : diff)
{
boost::json::object entry;
entry["object_id"] = ripple::strHex(obj.key);
if (binary)
if (input.binary)
{
entry["object"] = ripple::strHex(obj.blob);
}
else if (obj.blob.size())
{
ripple::STLedgerEntry sle{ripple::SerialIter{obj.blob.data(), obj.blob.size()}, obj.key};
ripple::STLedgerEntry const sle{ripple::SerialIter{obj.blob.data(), obj.blob.size()}, obj.key};
entry["object"] = toJson(sle);
}
else
{
entry["object"] = "";
}
jsonDiff.push_back(std::move(entry));
}
}
response[JS(ledger)] = header;
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
return response;
output.ledgerHash = ripple::strHex(lgrInfo.hash);
output.ledgerIndex = lgrInfo.seq;
return output;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerHandler::Output const& output)
{
jv = boost::json::object{
{JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
{JS(ledger), output.header},
};
}
LedgerHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class LedgerHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -52,7 +52,7 @@ public:
bool diff = false;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
LedgerHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -86,4 +86,4 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,98 +17,40 @@
*/
//==============================================================================
#include <rpc/handlers/LedgerData.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>
// Get state nodes from a ledger
// Inputs:
// limit: integer, maximum number of entries
// marker: opaque, resume point
// binary: boolean, format
// type: string // optional, defaults to all ledger node types
// Outputs:
// ledger_hash: chosen ledger's hash
// ledger_index: chosen ledger's index
// state: array of state nodes
// marker: resume point, if any
//
//
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
#include <algorithm>
namespace RPC {
using boost::json::value_to;
Result
doLedgerData(Context const& context)
LedgerDataHandler::Result
LedgerDataHandler::process(Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
// marker must be int if outOfOrder is true
if (input.outOfOrder && input.marker)
return Error{Status{RippledError::rpcINVALID_PARAMS, "outOfOrderMarkerNotInt"}};
bool const binary = getBool(request, "binary", false);
if (!input.outOfOrder && input.diffMarker)
return Error{Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}};
std::uint32_t limit;
if (auto const status = getLimit(context, limit); status)
return status;
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (!binary)
limit = std::clamp(limit, {1}, {256});
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
bool outOfOrder = false;
if (request.contains("out_of_order"))
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
// no marker -> first call, return header information
auto header = boost::json::object();
Output output;
if ((!input.marker) && (!input.diffMarker))
{
if (!request.at("out_of_order").is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
outOfOrder = request.at("out_of_order").as_bool();
}
std::optional<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)
if (input.binary)
{
header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
}
@@ -132,79 +74,155 @@ doLedgerData(Context const& context)
}
header[JS(closed)] = true;
response[JS(ledger)] = header;
output.header = std::move(header);
}
else
{
if (!outOfOrder && !context.backend->fetchLedgerObject(*marker, lgrInfo.seq, context.yield))
return Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"};
if (input.marker && !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield))
return Error{Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}};
}
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
output.ledgerHash = ripple::strHex(lgrInfo.hash);
output.ledgerIndex = lgrInfo.seq;
auto start = std::chrono::system_clock::now();
auto const start = std::chrono::system_clock::now();
std::vector<Backend::LedgerObject> results;
if (diffMarker)
if (input.diffMarker)
{
assert(outOfOrder);
auto diff = context.backend->fetchLedgerDiff(*diffMarker, context.yield);
// keep the same logic as previous implementation
auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield);
std::vector<ripple::uint256> keys;
for (auto&& [key, object] : diff)
for (auto& [key, object] : diff)
{
if (!object.size())
{
keys.push_back(std::move(key));
}
}
auto objs = context.backend->fetchLedgerObjects(keys, lgrInfo.seq, context.yield);
auto objs = sharedPtrBackend_->fetchLedgerObjects(keys, lgrInfo.seq, ctx.yield);
for (size_t i = 0; i < objs.size(); ++i)
{
auto&& obj = objs[i];
auto& obj = objs[i];
if (obj.size())
results.push_back({std::move(keys[i]), std::move(obj)});
}
if (*diffMarker > lgrInfo.seq)
response["marker"] = *diffMarker - 1;
if (*(input.diffMarker) > lgrInfo.seq)
output.diffMarker = *(input.diffMarker) - 1;
}
else
{
auto page = context.backend->fetchLedgerPage(marker, lgrInfo.seq, limit, outOfOrder, context.yield);
// limit's limitation is different based on binary or json
// framework can not handler the check right now, adjust the value here
auto const limit =
std::min(input.limit, input.binary ? LedgerDataHandler::LIMITBINARY : LedgerDataHandler::LIMITJSON);
auto page = sharedPtrBackend_->fetchLedgerPage(input.marker, lgrInfo.seq, limit, input.outOfOrder, ctx.yield);
results = std::move(page.objects);
if (page.cursor)
response["marker"] = ripple::strHex(*(page.cursor));
else if (outOfOrder)
response["marker"] = context.backend->fetchLedgerRange()->maxSequence;
output.marker = ripple::strHex(*(page.cursor));
else if (input.outOfOrder)
output.diffMarker = sharedPtrBackend_->fetchLedgerRange()->maxSequence;
}
auto end = std::chrono::system_clock::now();
auto time = std::chrono::duration_cast<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)
{
ripple::STLedgerEntry sle{ripple::SerialIter{object.data(), object.size()}, key};
if (binary)
ripple::STLedgerEntry const sle{ripple::SerialIter{object.data(), object.size()}, key};
if (input.binary)
{
boost::json::object entry;
entry[JS(data)] = ripple::serializeHex(sle);
entry[JS(index)] = ripple::to_string(sle.key());
objects.push_back(std::move(entry));
output.states.push_back(std::move(entry));
}
else
objects.push_back(toJson(sle));
{
output.states.push_back(toJson(sle));
}
}
response[JS(state)] = std::move(objects);
if (outOfOrder)
response["cache_full"] = context.backend->cache().isFull();
auto end2 = std::chrono::system_clock::now();
time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end).count();
gLog.debug() << "Number of results = " << results.size() << " serialized in " << time << " microseconds";
if (input.outOfOrder)
output.cacheFull = sharedPtrBackend_->cache().isFull();
return response;
auto const end2 = std::chrono::system_clock::now();
log_.debug() << "Number of results = " << results.size() << " serialized in "
<< std::chrono::duration_cast<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

View File

@@ -24,12 +24,17 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class LedgerDataHandler
{
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
clio::Logger log_{"RPC"};
// constants
static uint32_t constexpr LIMITBINARY = 2048;
static uint32_t constexpr LIMITJSON = 256;
public:
struct Output
{
@@ -57,7 +62,7 @@ public:
bool outOfOrder = false;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
LedgerDataHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{
@@ -83,13 +88,10 @@ public:
process(Input input, Context const& ctx) const;
private:
static uint32_t constexpr LIMITBINARY = 2048;
static uint32_t constexpr LIMITJSON = 256;
friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
friend Input
tag_invoke(boost::json::value_to_tag<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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,359 +17,223 @@
*/
//==============================================================================
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <boost/json.hpp>
#include <rpc/handlers/LedgerEntry.h>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
// {
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// ...
// }
#include <unordered_map>
namespace RPC {
using boost::json::value_to;
Result
doLedgerEntry(Context const& context)
LedgerEntryHandler::Result
LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
bool const binary = getBool(request, "binary", false);
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::uint256 key;
// the expected type of the entry object
auto expectedType = ripple::ltANY;
// Note: according to docs, only 1 of the below should be specified at any
// time. see https://xrpl.org/ledger_entry.html#ledger_entry
if (request.contains(JS(index)))
if (input.index)
{
if (!request.at(JS(index)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"};
if (!key.parseHex(request.at(JS(index)).as_string().c_str()))
return Status{ClioError::rpcMALFORMED_REQUEST};
key = ripple::uint256{std::string_view(*(input.index))};
}
else if (request.contains(JS(account_root)))
else if (input.accountRoot)
{
if (!request.at(JS(account_root)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "account_rootNotString"};
auto const account = ripple::parseBase58<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;
key = ripple::keylet::account(*ripple::parseBase58<ripple::AccountID>(*(input.accountRoot))).key;
}
else if (request.contains(JS(check)))
else if (input.directory)
{
if (!request.at(JS(check)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "checkNotString"};
auto const keyOrStatus = composeKeyFromDirectory(*input.directory);
if (auto const status = std::get_if<Status>(&keyOrStatus))
return Error{*status};
expectedType = ripple::ltCHECK;
if (!key.parseHex(request.at(JS(check)).as_string().c_str()))
{
return Status{RippledError::rpcINVALID_PARAMS, "checkMalformed"};
}
key = std::get<ripple::uint256>(keyOrStatus);
}
else if (request.contains(JS(deposit_preauth)))
else if (input.offer)
{
expectedType = ripple::ltDEPOSIT_PREAUTH;
if (!request.at(JS(deposit_preauth)).is_object())
{
if (!request.at(JS(deposit_preauth)).is_string() ||
!key.parseHex(request.at(JS(deposit_preauth)).as_string().c_str()))
{
return Status{RippledError::rpcINVALID_PARAMS, "deposit_preauthMalformed"};
}
}
else if (
!request.at(JS(deposit_preauth)).as_object().contains(JS(owner)) ||
!request.at(JS(deposit_preauth)).as_object().at(JS(owner)).is_string())
{
return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"};
}
else if (
!request.at(JS(deposit_preauth)).as_object().contains(JS(authorized)) ||
!request.at(JS(deposit_preauth)).as_object().at(JS(authorized)).is_string())
{
return Status{RippledError::rpcINVALID_PARAMS, "authorizedNotString"};
}
else
{
boost::json::object const& deposit_preauth = request.at(JS(deposit_preauth)).as_object();
auto const owner =
ripple::parseBase58<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;
}
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 (request.contains(JS(directory)))
else if (input.rippleStateAccount)
{
expectedType = ripple::ltDIR_NODE;
if (!request.at(JS(directory)).is_object())
{
if (!request.at(JS(directory)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "directoryNotString"};
if (!key.parseHex(request.at(JS(directory)).as_string().c_str()))
{
return Status{RippledError::rpcINVALID_PARAMS, "malformedDirectory"};
}
}
else if (
request.at(JS(directory)).as_object().contains(JS(sub_index)) &&
!request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64())
{
return Status{RippledError::rpcINVALID_PARAMS, "sub_indexNotInt"};
}
else
{
auto directory = request.at(JS(directory)).as_object();
std::uint64_t subIndex = directory.contains(JS(sub_index))
? boost::json::value_to<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"};
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 (request.contains(JS(ticket)))
else if (input.escrow)
{
expectedType = ripple::ltTICKET;
// ticket object : account, ticket_seq
if (!request.at(JS(ticket)).is_object())
{
if (!request.at(JS(ticket)).is_string())
return Status{ClioError::rpcMALFORMED_REQUEST, "ticketNotString"};
auto const id = ripple::parseBase58<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());
if (!key.parseHex(request.at(JS(ticket)).as_string().c_str()))
return Status{ClioError::rpcMALFORMED_REQUEST, "malformedTicket"};
}
else if (
!request.at(JS(ticket)).as_object().contains(JS(account)) ||
!request.at(JS(ticket)).as_object().at(JS(account)).is_string())
{
return Status{ClioError::rpcMALFORMED_REQUEST};
}
else if (
!request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) ||
!request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64())
{
return Status{ClioError::rpcMALFORMED_REQUEST, "malformedTicketSeq"};
}
else
{
auto const id = ripple::parseBase58<ripple::AccountID>(
request.at(JS(ticket)).as_object().at(JS(account)).as_string().c_str());
key = ripple::keylet::depositPreauth(*owner, *authorized).key;
}
else if (input.ticket)
{
auto const id = ripple::parseBase58<ripple::AccountID>(input.ticket->at(JS(account)).as_string().c_str());
if (!id)
return Status{ClioError::rpcMALFORMED_OWNER};
else
{
std::uint32_t seq = request.at(JS(ticket)).as_object().at(JS(ticket_seq)).as_int64();
key = ripple::getTicketIndex(*id, seq);
}
}
key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64());
}
else
{
return Status{RippledError::rpcINVALID_PARAMS, "unknownOption"};
// Must specify 1 of the following fields to indicate what type
return Error{Status{RippledError::rpcINVALID_PARAMS, "unknownOption"}};
}
auto dbResponse = context.backend->fetchLedgerObject(key, lgrInfo.seq, context.yield);
// check ledger exists
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (!dbResponse or dbResponse->size() == 0)
return Status{"entryNotFound"};
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
// check expected type matches actual type
ripple::STLedgerEntry sle{ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key};
if (expectedType != ripple::ltANY && sle.getType() != expectedType)
return Status{"unexpectedLedgerType"};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield);
response[JS(index)] = ripple::strHex(key);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
if (!ledgerObject || ledgerObject->size() == 0)
return Error{Status{"entryNotFound"}};
if (binary)
{
response[JS(node_binary)] = ripple::strHex(*dbResponse);
}
ripple::STLedgerEntry const sle{ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key};
if (input.expectedType != ripple::ltANY && sle.getType() != input.expectedType)
return Error{Status{"unexpectedLedgerType"}};
auto output = LedgerEntryHandler::Output{};
output.index = ripple::strHex(key);
output.ledgerIndex = lgrInfo.seq;
output.ledgerHash = ripple::strHex(lgrInfo.hash);
if (input.binary)
output.nodeBinary = ripple::strHex(*ledgerObject);
else
output.node = toJson(sle);
return output;
}
std::variant<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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class LedgerEntryHandler
{
@@ -63,7 +63,7 @@ public:
std::optional<boost::json::object> ticket;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
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() ||
!value.as_array()[1].is_string() ||
value.as_array()[0].as_string() == value.as_array()[1].as_string())
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "malformedAccounts"}};
{
return Error{Status{RippledError::rpcINVALID_PARAMS, "malformedAccounts"}};
}
auto const id1 = ripple::parseBase58<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());
if (!id1 || !id2)
return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}};
return Error{Status{ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"}};
return MaybeError{};
}};
@@ -156,7 +161,7 @@ public:
private:
// dir_root and owner can not be both empty or filled at the same time
// This function will return an error if this is the case
std::variant<ripple::uint256, RPC::Status>
std::variant<ripple::uint256, Status>
composeKeyFromDirectory(boost::json::object const& directory) const noexcept;
friend void
@@ -165,4 +170,5 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,28 +17,29 @@
*/
//==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/LedgerRange.h>
#include <optional>
namespace RPC {
Result
doLedgerRange(Context const& context)
LedgerRangeHandler::Result
LedgerRangeHandler::process() const
{
boost::json::object response = {};
auto range = context.backend->fetchLedgerRange();
if (!range)
{
return Status{RippledError::rpcNOT_READY, "rangeNotFound"};
}
if (auto const maybeRange = sharedPtrBackend_->fetchLedgerRange(); maybeRange)
return Output{*maybeRange};
else
{
response[JS(ledger_index_min)] = range->minSequence;
response[JS(ledger_index_max)] = range->maxSequence;
}
return Error{Status{RippledError::rpcNOT_READY, "rangeNotFound"}};
}
return response;
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerRangeHandler::Output const& output)
{
jv = boost::json::object{
{JS(ledger_index_min), output.range.minSequence},
{JS(ledger_index_max), output.range.maxSequence},
};
}
} // namespace RPC

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,50 +17,196 @@
*/
//==============================================================================
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/NFTHistory.h>
#include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
Result
doNFTHistory(Context const& context)
// TODO: this is currently very similar to account_tx but its own copy for time
// being. we should aim to reuse common logic in some way in the future.
NFTHistoryHandler::Result
NFTHistoryHandler::process(NFTHistoryHandler::Input input, Context const& ctx) const
{
auto const maybeTokenID = getNFTID(context.params);
if (auto const status = std::get_if<Status>(&maybeTokenID); status)
return *status;
auto const tokenID = std::get<ripple::uint256>(maybeTokenID);
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto [minIndex, maxIndex] = *range;
constexpr std::string_view outerFuncName = __func__;
auto const maybeResponse = traverseTransactions(
context,
[&tokenID, &outerFuncName](
std::shared_ptr<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 (input.ledgerIndexMin)
{
if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin)
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
if (auto const status = std::get_if<Status>(&maybeResponse); status)
return *status;
auto response = std::get<boost::json::object>(maybeResponse);
minIndex = *input.ledgerIndexMin;
}
if (input.ledgerIndexMax)
{
if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax)
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}};
maxIndex = *input.ledgerIndexMax;
}
if (minIndex > maxIndex)
return Error{Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"}};
if (input.ledgerHash || input.ledgerIndex)
{
// rippled does not have this check
if (input.ledgerIndexMax || input.ledgerIndexMin)
return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
if (auto status = std::get_if<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;
}
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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class NFTOffersHandlerBase
{
@@ -51,7 +51,7 @@ public:
std::optional<std::string> marker;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
NFTOffersHandlerBase(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -88,4 +88,4 @@ private:
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/ngHandlers/NFTSellOffers.h>
#include <rpc/handlers/NFTSellOffers.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h>
using namespace ripple;
namespace RPCng {
namespace RPC {
NFTSellOffersHandler::Result
NFTSellOffersHandler::process(NFTSellOffersHandler::Input input, Context const& ctx) const
{
auto const tokenID = uint256{input.nftID.c_str()};
auto const directory = keylet::nft_sells(tokenID);
return iterateOfferDirectory(input, tokenID, directory, ctx.yield);
}
} // namespace RPCng
} // namespace RPC

View File

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

View File

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

View File

@@ -26,7 +26,7 @@
#include <set>
namespace RPCng {
namespace RPC {
class NoRippleCheckHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -52,7 +52,7 @@ public:
bool transactions = false;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
NoRippleCheckHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -68,7 +68,7 @@ public:
validation::Required{},
validation::WithCustomError{
validation::OneOf{"gateway", "user"},
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "role field is invalid"}}},
Status{RippledError::rpcINVALID_PARAMS, "role field is invalid"}}},
{JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator},
{JS(limit), validation::Type<uint32_t>(), validation::Between{1, 500}},
@@ -88,4 +88,4 @@ private:
friend Input
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>
namespace RPCng {
namespace RPC {
class PingHandler
{
@@ -35,4 +35,5 @@ public:
return Output{};
}
};
} // namespace RPCng
} // namespace RPC

View File

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

View File

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

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;
}
namespace RPCng {
namespace RPC {
template <
typename SubscriptionManagerType,
@@ -133,19 +133,20 @@ public:
if (not fees.has_value())
return Error{Status{RippledError::rpcINTERNAL}};
auto output = Output{};
auto const sinceEpoch = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
auto const age = static_cast<int32_t>(sinceEpoch) -
static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
static_cast<int32_t>(rippleEpochStart);
Output output;
output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
if (ctx.isAdmin)
output.info.adminSection = {counters_.get().report(), subscriptions_->report(), etl_->getInfo()};
auto const serverInfoRippled =
balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield);
if (serverInfoRippled && !serverInfoRippled->contains(JS(error)))
{
if (serverInfoRippled->contains(JS(result)) &&
@@ -159,7 +160,6 @@ public:
output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash);
output.info.validatedLedger.seq = lgrInfo->seq;
output.info.validatedLedger.fees = fees;
output.info.cache.size = backend_->cache().size();
output.info.cache.isFull = backend_->cache().isFull();
output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
@@ -195,6 +195,7 @@ private:
try
{
auto const& rippledInfo = info.rippledInfo.value();
jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor));
jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version));
@@ -238,6 +239,6 @@ private:
}
};
using ServerInfoHandler = BaseServerInfoHandler<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/Validators.h>
namespace RPCng {
namespace RPC {
template <typename SubscriptionManagerType>
class BaseSubscribeHandler
{
@@ -40,7 +40,6 @@ public:
// books returns nothing by default, if snapshot is true, it returns offers
// TODO: use better type than json
std::optional<boost::json::array> offers;
bool validated = true;
};
struct OrderBook
@@ -59,7 +58,7 @@ public:
std::optional<std::vector<OrderBook>> books;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
BaseSubscribeHandler(
std::shared_ptr<BackendInterface> const& sharedPtrBackend,
@@ -74,27 +73,28 @@ public:
static auto const booksValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array())
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
for (auto const& book : value.as_array())
{
if (!book.is_object())
return Error{
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}};
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool())
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
if (book.as_object().contains("snapshot") && !book.as_object().at("snapshot").is_bool())
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}};
if (book.as_object().contains("taker"))
if (auto const err = validation::AccountValidator.verify(book.as_object(), "taker"); !err)
return err;
auto const parsedBook = RPC::parseBook(book.as_object());
if (auto const status = std::get_if<RPC::Status>(&parsedBook))
auto const parsedBook = parseBook(book.as_object());
if (auto const status = std::get_if<Status>(&parsedBook))
return Error(*status);
}
return MaybeError{};
}};
@@ -102,30 +102,37 @@ public:
{JS(streams), validation::SubscribeStreamValidator},
{JS(accounts), validation::SubscribeAccountsValidator},
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
{JS(books), booksValidator}};
{JS(books), booksValidator},
};
return rpcSpec;
}
Result
process(Input input, Context const& ctx) const
{
Output output;
auto output = Output{};
if (input.streams)
{
auto const ledger = subscribeToStreams(ctx.yield, *(input.streams), ctx.session);
if (!ledger.empty())
output.ledger = ledger;
}
if (input.accounts)
subscribeToAccounts(*(input.accounts), ctx.session);
if (input.accountsProposed)
subscribeToAccountsProposed(*(input.accountsProposed), ctx.session);
if (input.books)
{
auto const offers = subscribeToBooks(*(input.books), ctx.session, ctx.yield);
if (!offers.empty())
output.offers = offers;
};
return output;
}
@@ -136,7 +143,8 @@ private:
std::vector<std::string> const& streams,
std::shared_ptr<WsBase> const& session) const
{
boost::json::object response;
auto response = boost::json::object{};
for (auto const& stream : streams)
{
if (stream == "ledger")
@@ -152,6 +160,7 @@ private:
else if (stream == "book_changes")
subscriptions_->subBookChanges(session);
}
return response;
}
@@ -160,16 +169,17 @@ private:
{
for (auto const& account : accounts)
{
auto const accountID = RPC::accountFromStringStrict(account);
auto const accountID = accountFromStringStrict(account);
subscriptions_->subAccount(*accountID, session);
}
}
void
subscribeToAccountsProposed(std::vector<std::string> const& accounts, std::shared_ptr<WsBase> const& session) const
{
for (auto const& account : accounts)
{
auto const accountID = RPC::accountFromStringStrict(account);
auto const accountID = accountFromStringStrict(account);
subscriptions_->subProposedAccount(*accountID, session);
}
}
@@ -180,9 +190,10 @@ private:
std::shared_ptr<WsBase> const& session,
boost::asio::yield_context& yield) const
{
static auto constexpr fetchLimit = 200;
boost::json::array snapshots;
std::optional<Backend::LedgerRange> rng;
static auto constexpr fetchLimit = 200;
for (auto const& internalBook : books)
{
@@ -190,6 +201,7 @@ private:
{
if (!rng)
rng = sharedPtrBackend_->fetchLedgerRange();
auto const getOrderBook = [&](auto const& book) {
auto const bookBase = getBookBase(book);
auto const [offers, _] =
@@ -198,18 +210,21 @@ private:
// the taker is not really uesed, same issue with
// https://github.com/XRPLF/xrpl-dev-portal/issues/1818
auto const takerID =
internalBook.taker ? RPC::accountFromStringStrict(*(internalBook.taker)) : beast::zero;
internalBook.taker ? accountFromStringStrict(*(internalBook.taker)) : beast::zero;
auto const orderBook =
RPC::postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield);
postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield);
std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshots));
};
getOrderBook(internalBook.book);
if (internalBook.both)
getOrderBook(ripple::reversed(internalBook.book));
}
subscriptions_->subBook(internalBook.book, session);
if (internalBook.both)
subscriptions_->subBook(ripple::reversed(internalBook.book), session);
}
@@ -221,6 +236,7 @@ private:
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
{
jv = output.ledger ? *(output.ledger) : boost::json::object();
if (output.offers)
jv.as_object().emplace(JS(offers), *(output.offers));
}
@@ -228,48 +244,57 @@ private:
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
{
auto input = Input{};
auto const& jsonObject = jv.as_object();
Input input;
if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end())
{
input.streams = std::vector<std::string>();
for (auto const& stream : streams->value().as_array())
input.streams->push_back(stream.as_string().c_str());
}
if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end())
{
input.accounts = std::vector<std::string>();
for (auto const& account : accounts->value().as_array())
input.accounts->push_back(account.as_string().c_str());
}
if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed)); accountsProposed != jsonObject.end())
{
input.accountsProposed = std::vector<std::string>();
for (auto const& account : accountsProposed->value().as_array())
input.accountsProposed->push_back(account.as_string().c_str());
}
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end())
{
input.books = std::vector<OrderBook>();
for (auto const& book : books->value().as_array())
{
auto internalBook = OrderBook{};
auto const& bookObject = book.as_object();
OrderBook internalBook;
if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end())
internalBook.taker = taker->value().as_string().c_str();
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
internalBook.both = both->value().as_bool();
if (auto const& snapshot = bookObject.find(JS(snapshot)); snapshot != bookObject.end())
internalBook.snapshot = snapshot->value().as_bool();
auto const parsedBookMaybe = RPC::parseBook(book.as_object());
auto const parsedBookMaybe = parseBook(book.as_object());
internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
input.books->push_back(internalBook);
}
}
return input;
}
};
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,25 +17,22 @@
*/
//==============================================================================
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/TransactionEntry.h>
namespace RPC {
Result
doTransactionEntry(Context const& context)
TransactionEntryHandler::Result
TransactionEntryHandler::process(TransactionEntryHandler::Input input, Context const& ctx) const
{
boost::json::object response;
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (auto status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
ripple::uint256 hash;
if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash))))
return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const dbRet = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.txHash.c_str()}, ctx.yield);
// Note: transaction_entry is meant to only search a specified ledger for
// the specified transaction. tx searches the entire range of history. For
// rippled, having two separate commands made sense, as tx would use SQLite
@@ -45,15 +42,51 @@ doTransactionEntry(Context const& context)
// the API for transaction_entry says the method only searches the specified
// ledger; we simulate that here by returning not found if the transaction
// is in a different ledger than the one specified.
if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq)
return Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."};
if (!dbRet || dbRet->ledgerSequence != lgrInfo.seq)
return Error{Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}};
auto [txn, meta] = toExpandedJson(*dbResponse);
response[JS(tx_json)] = std::move(txn);
response[JS(metadata)] = std::move(meta);
response[JS(ledger_index)] = lgrInfo.seq;
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
return response;
auto output = TransactionEntryHandler::Output{};
auto [txn, meta] = toExpandedJson(*dbRet);
output.tx = std::move(txn);
output.metadata = std::move(meta);
output.ledgerIndex = lgrInfo.seq;
output.ledgerHash = ripple::strHex(lgrInfo.hash);
return output;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TransactionEntryHandler::Output const& output)
{
jv = {
{JS(metadata), output.metadata},
{JS(tx_json), output.tx},
{JS(ledger_index), output.ledgerIndex},
{JS(ledger_hash), output.ledgerHash},
};
}
TransactionEntryHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

@@ -24,7 +24,7 @@
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
namespace RPCng {
namespace RPC {
class TransactionEntryHandler
{
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -48,7 +48,7 @@ public:
std::optional<uint32_t> ledgerIndex;
};
using Result = RPCng::HandlerReturnType<Output>;
using Result = HandlerReturnType<Output>;
TransactionEntryHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
@@ -77,4 +77,5 @@ private:
friend Input
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
Copyright (c) 2022, the clio developers.
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,84 +17,106 @@
*/
//==============================================================================
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <rpc/handlers/Tx.h>
namespace RPC {
// {
// transaction: <hex>
// }
Result
doTx(Context const& context)
TxHandler::Result
TxHandler::process(Input input, Context const& ctx) const
{
auto request = context.params;
boost::json::object response = {};
if (!request.contains(JS(transaction)))
return Status{RippledError::rpcINVALID_PARAMS, "specifyTransaction"};
if (!request.at(JS(transaction)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "transactionNotString"};
ripple::uint256 hash;
if (!hash.parseHex(request.at(JS(transaction)).as_string().c_str()))
return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"};
bool binary = false;
if (request.contains(JS(binary)))
{
if (!request.at(JS(binary)).is_bool())
return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at(JS(binary)).as_bool();
}
auto minLedger = getUInt(request, JS(min_ledger));
auto maxLedger = getUInt(request, JS(max_ledger));
bool rangeSupplied = minLedger && maxLedger;
constexpr static auto maxLedgerRange = 1000u;
auto const rangeSupplied = input.minLedger && input.maxLedger;
if (rangeSupplied)
{
if (*minLedger > *maxLedger)
return Status{RippledError::rpcINVALID_LGR_RANGE};
if (*maxLedger - *minLedger > 1000)
return Status{RippledError::rpcEXCESSIVE_LGR_RANGE};
if (*input.minLedger > *input.maxLedger)
return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
if (*input.maxLedger - *input.minLedger > maxLedgerRange)
return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
}
auto range = context.backend->fetchLedgerRange();
if (!range)
return Status{RippledError::rpcNOT_READY};
auto output = TxHandler::Output{};
auto const dbResponse =
sharedPtrBackend_->fetchTransaction(ripple::uint256{std::string_view(input.transaction)}, ctx.yield);
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
if (!dbResponse)
{
if (rangeSupplied)
{
bool searchedAll = range->maxSequence >= *maxLedger && range->minSequence <= *minLedger;
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const searchedAll = range->maxSequence >= *input.maxLedger && range->minSequence <= *input.minLedger;
boost::json::object extra;
extra["searched_all"] = searchedAll;
return Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)};
return Error{Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}};
}
return Status{RippledError::rpcTXN_NOT_FOUND};
return Error{Status{RippledError::rpcTXN_NOT_FOUND}};
}
if (!binary)
// clio does not implement 'inLedger' which is a deprecated field
if (!input.binary)
{
auto [txn, meta] = toExpandedJson(*dbResponse);
response = txn;
response[JS(meta)] = meta;
auto const [txn, meta] = toExpandedJson(*dbResponse);
output.tx = txn;
output.meta = meta;
}
else
{
response[JS(tx)] = ripple::strHex(dbResponse->transaction);
response[JS(meta)] = ripple::strHex(dbResponse->metadata);
response[JS(hash)] = std::move(request.at(JS(transaction)).as_string());
output.txStr = ripple::strHex(dbResponse->transaction);
output.metaStr = ripple::strHex(dbResponse->metadata);
output.hash = std::move(input.transaction);
}
response[JS(date)] = dbResponse->date;
response[JS(ledger_index)] = dbResponse->ledgerSequence;
return response;
output.date = dbResponse->date;
output.ledgerIndex = dbResponse->ledgerSequence;
return output;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TxHandler::Output const& output)
{
auto obj = boost::json::object{};
if (output.tx)
{
obj = *output.tx;
obj["meta"] = *output.meta;
}
else
{
obj["meta"] = *output.metaStr;
obj["tx"] = *output.txStr;
obj["hash"] = output.hash;
}
obj["date"] = output.date;
obj["ledger_index"] = output.ledgerIndex;
jv = std::move(obj);
}
TxHandler::Input
tag_invoke(boost::json::value_to_tag<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

View File

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