mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-03 01:55:54 +00:00
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
279
src/rpc/RPC.cpp
279
src/rpc/RPC.cpp
@@ -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
|
||||
|
||||
170
src/rpc/RPC.h
170
src/rpc/RPC.h
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
115
src/rpc/common/impl/HandlerProvider.cpp
Normal file
115
src/rpc/common/impl/HandlerProvider.cpp
Normal 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
|
||||
73
src/rpc/common/impl/HandlerProvider.h
Normal file
73
src/rpc/common/impl/HandlerProvider.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user