mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -39,7 +39,9 @@ target_sources(
|
||||
handlers/NFTSellOffers.cpp
|
||||
handlers/NoRippleCheck.cpp
|
||||
handlers/Random.cpp
|
||||
handlers/Subscribe.cpp
|
||||
handlers/TransactionEntry.cpp
|
||||
handlers/Unsubscribe.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(clio_rpc PRIVATE clio_util)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "data/DBHelpers.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "main/Build.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
@@ -51,9 +52,6 @@ namespace etl {
|
||||
class ETLService;
|
||||
class LoadBalancer;
|
||||
} // namespace etl
|
||||
namespace feed {
|
||||
class SubscriptionManager;
|
||||
} // namespace feed
|
||||
namespace rpc {
|
||||
class Counters;
|
||||
} // namespace rpc
|
||||
@@ -63,17 +61,16 @@ namespace rpc {
|
||||
/**
|
||||
* @brief Contains common functionality for handling the `server_info` command
|
||||
*
|
||||
* @tparam SubscriptionManagerType The type of the subscription manager
|
||||
* @tparam LoadBalancerType The type of the load balancer
|
||||
* @tparam ETLServiceType The type of the ETL service
|
||||
* @tparam CountersType The type of the counters
|
||||
*/
|
||||
template <typename SubscriptionManagerType, typename LoadBalancerType, typename ETLServiceType, typename CountersType>
|
||||
template <typename LoadBalancerType, typename ETLServiceType, typename CountersType>
|
||||
class BaseServerInfoHandler {
|
||||
static constexpr auto BACKEND_COUNTERS_KEY = "backend_counters";
|
||||
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManagerType> subscriptions_;
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
|
||||
std::shared_ptr<LoadBalancerType> balancer_;
|
||||
std::shared_ptr<ETLServiceType const> etl_;
|
||||
std::reference_wrapper<CountersType const> counters_;
|
||||
@@ -159,7 +156,7 @@ public:
|
||||
*/
|
||||
BaseServerInfoHandler(
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManagerType> const& subscriptions,
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions,
|
||||
std::shared_ptr<LoadBalancerType> const& balancer,
|
||||
std::shared_ptr<ETLServiceType const> const& etl,
|
||||
CountersType const& counters
|
||||
@@ -352,7 +349,6 @@ private:
|
||||
*
|
||||
* For more details see: https://xrpl.org/server_info-clio.html
|
||||
*/
|
||||
using ServerInfoHandler =
|
||||
BaseServerInfoHandler<feed::SubscriptionManager, etl::LoadBalancer, etl::ETLService, Counters>;
|
||||
using ServerInfoHandler = BaseServerInfoHandler<etl::LoadBalancer, etl::ETLService, Counters>;
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
303
src/rpc/handlers/Subscribe.cpp
Normal file
303
src/rpc/handlers/Subscribe.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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/handlers/Subscribe.hpp"
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "data/Types.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <ripple/beast/utility/Zero.h>
|
||||
#include <ripple/protocol/Book.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
SubscribeHandler::SubscribeHandler(
|
||||
std::shared_ptr<BackendInterface> const& sharedPtrBackend,
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions
|
||||
)
|
||||
: sharedPtrBackend_(sharedPtrBackend), subscriptions_(subscriptions)
|
||||
{
|
||||
}
|
||||
|
||||
RpcSpecConstRef
|
||||
SubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
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{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
if (book.as_object().contains("snapshot") && !book.as_object().at("snapshot").is_bool())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}};
|
||||
|
||||
if (book.as_object().contains("taker")) {
|
||||
if (auto err = meta::WithCustomError(
|
||||
validation::AccountValidator,
|
||||
Status{RippledError::rpcBAD_ISSUER, "Issuer account malformed."}
|
||||
)
|
||||
.verify(book.as_object(), "taker");
|
||||
!err)
|
||||
return err;
|
||||
}
|
||||
|
||||
auto const parsedBook = parseBook(book.as_object());
|
||||
if (auto const status = std::get_if<Status>(&parsedBook))
|
||||
return Error(*status);
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(streams), validation::SubscribeStreamValidator},
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{"user", check::Deprecated{}},
|
||||
{JS(password), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}}
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
}
|
||||
|
||||
SubscribeHandler::Result
|
||||
SubscribeHandler::process(Input input, Context const& ctx) const
|
||||
{
|
||||
auto output = Output{};
|
||||
|
||||
// Mimic rippled. No matter what the request is, the api version changes for the whole session
|
||||
ctx.session->apiSubVersion = ctx.apiVersion;
|
||||
|
||||
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)
|
||||
subscribeToBooks(*(input.books), ctx.session, ctx.yield, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
SubscribeHandler::subscribeToStreams(
|
||||
boost::asio::yield_context yield,
|
||||
std::vector<std::string> const& streams,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
auto response = boost::json::object{};
|
||||
|
||||
for (auto const& stream : streams) {
|
||||
if (stream == "ledger") {
|
||||
response = subscriptions_->subLedger(yield, session);
|
||||
} else if (stream == "transactions") {
|
||||
subscriptions_->subTransactions(session);
|
||||
} else if (stream == "transactions_proposed") {
|
||||
subscriptions_->subProposedTransactions(session);
|
||||
} else if (stream == "validations") {
|
||||
subscriptions_->subValidation(session);
|
||||
} else if (stream == "manifests") {
|
||||
subscriptions_->subManifest(session);
|
||||
} else if (stream == "book_changes") {
|
||||
subscriptions_->subBookChanges(session);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void
|
||||
SubscribeHandler::subscribeToAccountsProposed(
|
||||
std::vector<std::string> const& accounts,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->subProposedAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SubscribeHandler::subscribeToAccounts(
|
||||
std::vector<std::string> const& accounts,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->subAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SubscribeHandler::subscribeToBooks(
|
||||
std::vector<OrderBook> const& books,
|
||||
std::shared_ptr<web::ConnectionBase> const& session,
|
||||
boost::asio::yield_context yield,
|
||||
Output& output
|
||||
) const
|
||||
{
|
||||
static auto constexpr fetchLimit = 200;
|
||||
|
||||
std::optional<data::LedgerRange> rng;
|
||||
|
||||
for (auto const& internalBook : books) {
|
||||
if (internalBook.snapshot) {
|
||||
if (!rng)
|
||||
rng = sharedPtrBackend_->fetchLedgerRange();
|
||||
|
||||
auto const getOrderBook = [&](auto const& book, auto& snapshots) {
|
||||
auto const bookBase = getBookBase(book);
|
||||
auto const [offers, _] =
|
||||
sharedPtrBackend_->fetchBookOffers(bookBase, rng->maxSequence, fetchLimit, yield);
|
||||
|
||||
// the taker is not really uesed, same issue with
|
||||
// https://github.com/XRPLF/xrpl-dev-portal/issues/1818
|
||||
auto const takerID = internalBook.taker ? accountFromStringStrict(*(internalBook.taker)) : beast::zero;
|
||||
|
||||
auto const orderBook =
|
||||
postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield);
|
||||
std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshots));
|
||||
};
|
||||
|
||||
if (internalBook.both) {
|
||||
if (!output.bids)
|
||||
output.bids = boost::json::array();
|
||||
if (!output.asks)
|
||||
output.asks = boost::json::array();
|
||||
getOrderBook(internalBook.book, *(output.bids));
|
||||
getOrderBook(ripple::reversed(internalBook.book), *(output.asks));
|
||||
} else {
|
||||
if (!output.offers)
|
||||
output.offers = boost::json::array();
|
||||
getOrderBook(internalBook.book, *(output.offers));
|
||||
}
|
||||
}
|
||||
|
||||
subscriptions_->subBook(internalBook.book, session);
|
||||
|
||||
if (internalBook.both)
|
||||
subscriptions_->subBook(ripple::reversed(internalBook.book), session);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, SubscribeHandler::Output const& output)
|
||||
{
|
||||
jv = output.ledger ? *(output.ledger) : boost::json::object();
|
||||
|
||||
if (output.offers)
|
||||
jv.as_object().emplace(JS(offers), *(output.offers));
|
||||
if (output.asks)
|
||||
jv.as_object().emplace(JS(asks), *(output.asks));
|
||||
if (output.bids)
|
||||
jv.as_object().emplace(JS(bids), *(output.bids));
|
||||
}
|
||||
|
||||
SubscribeHandler::Input
|
||||
tag_invoke(boost::json::value_to_tag<SubscribeHandler::Input>, boost::json::value const& jv)
|
||||
{
|
||||
auto input = SubscribeHandler::Input{};
|
||||
auto const& jsonObject = jv.as_object();
|
||||
|
||||
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(boost::json::value_to<std::string>(stream));
|
||||
}
|
||||
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
|
||||
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) {
|
||||
input.books = std::vector<SubscribeHandler::OrderBook>();
|
||||
for (auto const& book : books->value().as_array()) {
|
||||
auto internalBook = SubscribeHandler::OrderBook{};
|
||||
auto const& bookObject = book.as_object();
|
||||
|
||||
if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end())
|
||||
internalBook.taker = boost::json::value_to<std::string>(taker->value());
|
||||
|
||||
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 = parseBook(book.as_object());
|
||||
internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
|
||||
input.books->push_back(internalBook);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
@@ -20,14 +20,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "data/Types.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
@@ -44,25 +39,20 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace feed {
|
||||
class SubscriptionManager;
|
||||
} // namespace feed
|
||||
|
||||
namespace rpc {
|
||||
|
||||
/**
|
||||
* @brief Contains functionality for handling the `subscribe` command
|
||||
* @brief Contains functionality for handling the `subscribe` command.
|
||||
* The subscribe method requests periodic notifications from the server when certain events happen.
|
||||
*
|
||||
* @tparam SubscriptionManagerType The type of the subscription manager to use
|
||||
* For more details see: https://xrpl.org/subscribe.html
|
||||
*/
|
||||
template <typename SubscriptionManagerType>
|
||||
class BaseSubscribeHandler {
|
||||
|
||||
class SubscribeHandler {
|
||||
std::shared_ptr<BackendInterface> sharedPtrBackend_;
|
||||
std::shared_ptr<SubscriptionManagerType> subscriptions_;
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -109,13 +99,10 @@ public:
|
||||
* @param sharedPtrBackend The backend to use
|
||||
* @param subscriptions The subscription manager to use
|
||||
*/
|
||||
BaseSubscribeHandler(
|
||||
SubscribeHandler(
|
||||
std::shared_ptr<BackendInterface> const& sharedPtrBackend,
|
||||
std::shared_ptr<SubscriptionManagerType> const& subscriptions
|
||||
)
|
||||
: sharedPtrBackend_(sharedPtrBackend), subscriptions_(subscriptions)
|
||||
{
|
||||
}
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Returns the API specification for the command
|
||||
@@ -124,53 +111,7 @@ public:
|
||||
* @return The spec for the given apiVersion
|
||||
*/
|
||||
static RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
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{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
if (book.as_object().contains("snapshot") && !book.as_object().at("snapshot").is_bool())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "snapshotNotBool"}};
|
||||
|
||||
if (book.as_object().contains("taker")) {
|
||||
if (auto err = meta::WithCustomError(
|
||||
validation::AccountValidator,
|
||||
Status{RippledError::rpcBAD_ISSUER, "Issuer account malformed."}
|
||||
)
|
||||
.verify(book.as_object(), "taker");
|
||||
!err)
|
||||
return err;
|
||||
}
|
||||
|
||||
auto const parsedBook = parseBook(book.as_object());
|
||||
if (auto const status = std::get_if<Status>(&parsedBook))
|
||||
return Error(*status);
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(streams), validation::SubscribeStreamValidator},
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{"user", check::Deprecated{}},
|
||||
{JS(password), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}}
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
}
|
||||
spec([[maybe_unused]] uint32_t apiVersion);
|
||||
|
||||
/**
|
||||
* @brief Process the Subscribe command
|
||||
@@ -180,30 +121,7 @@ public:
|
||||
* @return The result of the operation
|
||||
*/
|
||||
Result
|
||||
process(Input input, Context const& ctx) const
|
||||
{
|
||||
auto output = Output{};
|
||||
|
||||
// Mimic rippled. No matter what the request is, the api version changes for the whole session
|
||||
ctx.session->apiSubVersion = ctx.apiVersion;
|
||||
|
||||
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)
|
||||
subscribeToBooks(*(input.books), ctx.session, ctx.yield, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
process(Input input, Context const& ctx) const;
|
||||
|
||||
private:
|
||||
boost::json::object
|
||||
@@ -211,50 +129,17 @@ private:
|
||||
boost::asio::yield_context yield,
|
||||
std::vector<std::string> const& streams,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
auto response = boost::json::object{};
|
||||
|
||||
for (auto const& stream : streams) {
|
||||
if (stream == "ledger") {
|
||||
response = subscriptions_->subLedger(yield, session);
|
||||
} else if (stream == "transactions") {
|
||||
subscriptions_->subTransactions(session);
|
||||
} else if (stream == "transactions_proposed") {
|
||||
subscriptions_->subProposedTransactions(session);
|
||||
} else if (stream == "validations") {
|
||||
subscriptions_->subValidation(session);
|
||||
} else if (stream == "manifests") {
|
||||
subscriptions_->subManifest(session);
|
||||
} else if (stream == "book_changes") {
|
||||
subscriptions_->subBookChanges(session);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
) const;
|
||||
|
||||
void
|
||||
subscribeToAccounts(std::vector<std::string> const& accounts, std::shared_ptr<web::ConnectionBase> const& session)
|
||||
const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->subAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
const;
|
||||
|
||||
void
|
||||
subscribeToAccountsProposed(
|
||||
std::vector<std::string> const& accounts,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->subProposedAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
) const;
|
||||
|
||||
void
|
||||
subscribeToBooks(
|
||||
@@ -262,121 +147,25 @@ private:
|
||||
std::shared_ptr<web::ConnectionBase> const& session,
|
||||
boost::asio::yield_context yield,
|
||||
Output& output
|
||||
) const
|
||||
{
|
||||
static auto constexpr fetchLimit = 200;
|
||||
|
||||
std::optional<data::LedgerRange> rng;
|
||||
|
||||
for (auto const& internalBook : books) {
|
||||
if (internalBook.snapshot) {
|
||||
if (!rng)
|
||||
rng = sharedPtrBackend_->fetchLedgerRange();
|
||||
|
||||
auto const getOrderBook = [&](auto const& book, auto& snapshots) {
|
||||
auto const bookBase = getBookBase(book);
|
||||
auto const [offers, _] =
|
||||
sharedPtrBackend_->fetchBookOffers(bookBase, rng->maxSequence, fetchLimit, yield);
|
||||
|
||||
// the taker is not really uesed, same issue with
|
||||
// https://github.com/XRPLF/xrpl-dev-portal/issues/1818
|
||||
auto const takerID =
|
||||
internalBook.taker ? accountFromStringStrict(*(internalBook.taker)) : beast::zero;
|
||||
|
||||
auto const orderBook =
|
||||
postProcessOrderBook(offers, book, *takerID, *sharedPtrBackend_, rng->maxSequence, yield);
|
||||
std::copy(orderBook.begin(), orderBook.end(), std::back_inserter(snapshots));
|
||||
};
|
||||
|
||||
if (internalBook.both) {
|
||||
if (!output.bids)
|
||||
output.bids = boost::json::array();
|
||||
if (!output.asks)
|
||||
output.asks = boost::json::array();
|
||||
getOrderBook(internalBook.book, *(output.bids));
|
||||
getOrderBook(ripple::reversed(internalBook.book), *(output.asks));
|
||||
} else {
|
||||
if (!output.offers)
|
||||
output.offers = boost::json::array();
|
||||
getOrderBook(internalBook.book, *(output.offers));
|
||||
}
|
||||
}
|
||||
|
||||
subscriptions_->subBook(internalBook.book, session);
|
||||
|
||||
if (internalBook.both)
|
||||
subscriptions_->subBook(ripple::reversed(internalBook.book), session);
|
||||
}
|
||||
}
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Convert output to json value
|
||||
*
|
||||
* @param jv The json value to convert to
|
||||
* @param output The output to convert from
|
||||
*/
|
||||
friend void
|
||||
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));
|
||||
if (output.asks)
|
||||
jv.as_object().emplace(JS(asks), *(output.asks));
|
||||
if (output.bids)
|
||||
jv.as_object().emplace(JS(bids), *(output.bids));
|
||||
}
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
|
||||
|
||||
/**
|
||||
* @brief Convert json value to input
|
||||
*
|
||||
* @param jv The json value to convert from
|
||||
* @return The input to convert to
|
||||
*/
|
||||
friend Input
|
||||
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
|
||||
{
|
||||
auto input = Input{};
|
||||
auto const& jsonObject = jv.as_object();
|
||||
|
||||
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(boost::json::value_to<std::string>(stream));
|
||||
}
|
||||
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (auto const& taker = bookObject.find(JS(taker)); taker != bookObject.end())
|
||||
internalBook.taker = boost::json::value_to<std::string>(taker->value());
|
||||
|
||||
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 = parseBook(book.as_object());
|
||||
internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
|
||||
input.books->push_back(internalBook);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The subscribe method requests periodic notifications from the server when certain events happen.
|
||||
*
|
||||
* For more details see: https://xrpl.org/subscribe.html
|
||||
*/
|
||||
using SubscribeHandler = BaseSubscribeHandler<feed::SubscriptionManager>;
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
208
src/rpc/handlers/Unsubscribe.cpp
Normal file
208
src/rpc/handlers/Unsubscribe.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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/handlers/Unsubscribe.hpp"
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <ripple/protocol/Book.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
UnsubscribeHandler::UnsubscribeHandler(
|
||||
std::shared_ptr<BackendInterface> const& sharedPtrBackend,
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions
|
||||
)
|
||||
: sharedPtrBackend_(sharedPtrBackend), subscriptions_(subscriptions)
|
||||
{
|
||||
}
|
||||
|
||||
RpcSpecConstRef
|
||||
UnsubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
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{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
auto const parsedBook = parseBook(book.as_object());
|
||||
if (auto const status = std::get_if<Status>(&parsedBook))
|
||||
return Error(*status);
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(streams), validation::SubscribeStreamValidator},
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{JS(url), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}},
|
||||
{"rt_transactions", check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
}
|
||||
|
||||
UnsubscribeHandler::Result
|
||||
UnsubscribeHandler::process(Input input, Context const& ctx) const
|
||||
{
|
||||
if (input.streams)
|
||||
unsubscribeFromStreams(*(input.streams), ctx.session);
|
||||
|
||||
if (input.accounts)
|
||||
unsubscribeFromAccounts(*(input.accounts), ctx.session);
|
||||
|
||||
if (input.accountsProposed)
|
||||
unsubscribeFromProposedAccounts(*(input.accountsProposed), ctx.session);
|
||||
|
||||
if (input.books)
|
||||
unsubscribeFromBooks(*(input.books), ctx.session);
|
||||
|
||||
return Output{};
|
||||
}
|
||||
void
|
||||
UnsubscribeHandler::unsubscribeFromStreams(
|
||||
std::vector<std::string> const& streams,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& stream : streams) {
|
||||
if (stream == "ledger") {
|
||||
subscriptions_->unsubLedger(session);
|
||||
} else if (stream == "transactions") {
|
||||
subscriptions_->unsubTransactions(session);
|
||||
} else if (stream == "transactions_proposed") {
|
||||
subscriptions_->unsubProposedTransactions(session);
|
||||
} else if (stream == "validations") {
|
||||
subscriptions_->unsubValidation(session);
|
||||
} else if (stream == "manifests") {
|
||||
subscriptions_->unsubManifest(session);
|
||||
} else if (stream == "book_changes") {
|
||||
subscriptions_->unsubBookChanges(session);
|
||||
} else {
|
||||
ASSERT(false, "Unknown stream: {}", stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
void
|
||||
UnsubscribeHandler::unsubscribeFromAccounts(
|
||||
std::vector<std::string> accounts,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->unsubAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
void
|
||||
UnsubscribeHandler::unsubscribeFromProposedAccounts(
|
||||
std::vector<std::string> accountsProposed,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accountsProposed) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->unsubProposedAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
void
|
||||
UnsubscribeHandler::unsubscribeFromBooks(
|
||||
std::vector<OrderBook> const& books,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& orderBook : books) {
|
||||
subscriptions_->unsubBook(orderBook.book, session);
|
||||
|
||||
if (orderBook.both)
|
||||
subscriptions_->unsubBook(ripple::reversed(orderBook.book), session);
|
||||
}
|
||||
}
|
||||
|
||||
UnsubscribeHandler::Input
|
||||
tag_invoke(boost::json::value_to_tag<UnsubscribeHandler::Input>, boost::json::value const& jv)
|
||||
{
|
||||
auto input = UnsubscribeHandler::Input{};
|
||||
auto const& jsonObject = jv.as_object();
|
||||
|
||||
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(boost::json::value_to<std::string>(stream));
|
||||
}
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) {
|
||||
input.books = std::vector<UnsubscribeHandler::OrderBook>();
|
||||
for (auto const& book : books->value().as_array()) {
|
||||
auto internalBook = UnsubscribeHandler::OrderBook{};
|
||||
auto const& bookObject = book.as_object();
|
||||
|
||||
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
|
||||
internalBook.both = both->value().as_bool();
|
||||
|
||||
auto const parsedBookMaybe = parseBook(book.as_object());
|
||||
internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
|
||||
input.books->push_back(internalBook);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
} // namespace rpc
|
||||
@@ -20,17 +20,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <ripple/protocol/Book.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
@@ -39,8 +35,6 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace feed {
|
||||
@@ -50,12 +44,16 @@ class SubscriptionManager;
|
||||
namespace rpc {
|
||||
|
||||
/**
|
||||
* @brief Handles the `unsubscribe` command which is used to disconnect a subscriber from a feed
|
||||
* @brief Handles the `unsubscribe` command which is used to disconnect a subscriber from a feed.
|
||||
* The unsubscribe command tells the server to stop sending messages for a particular subscription or set of
|
||||
* subscriptions.
|
||||
*
|
||||
* For more details see: https://xrpl.org/unsubscribe.html
|
||||
*/
|
||||
template <typename SubscriptionManagerType>
|
||||
class BaseUnsubscribeHandler {
|
||||
|
||||
class UnsubscribeHandler {
|
||||
std::shared_ptr<BackendInterface> sharedPtrBackend_;
|
||||
std::shared_ptr<SubscriptionManagerType> subscriptions_;
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -85,13 +83,10 @@ public:
|
||||
* @param sharedPtrBackend The backend to use
|
||||
* @param subscriptions The subscription manager to use
|
||||
*/
|
||||
BaseUnsubscribeHandler(
|
||||
UnsubscribeHandler(
|
||||
std::shared_ptr<BackendInterface> const& sharedPtrBackend,
|
||||
std::shared_ptr<SubscriptionManagerType> const& subscriptions
|
||||
)
|
||||
: sharedPtrBackend_(sharedPtrBackend), subscriptions_(subscriptions)
|
||||
{
|
||||
}
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Returns the API specification for the command
|
||||
@@ -100,40 +95,7 @@ public:
|
||||
* @return The spec for the given apiVersion
|
||||
*/
|
||||
static RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}};
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
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{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
auto const parsedBook = parseBook(book.as_object());
|
||||
if (auto const status = std::get_if<Status>(&parsedBook))
|
||||
return Error(*status);
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(streams), validation::SubscribeStreamValidator},
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{JS(url), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}},
|
||||
{"rt_transactions", check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
}
|
||||
spec([[maybe_unused]] uint32_t apiVersion);
|
||||
|
||||
/**
|
||||
* @brief Process the Unsubscribe command
|
||||
@@ -143,127 +105,35 @@ public:
|
||||
* @return The result of the operation
|
||||
*/
|
||||
Result
|
||||
process(Input input, Context const& ctx) const
|
||||
{
|
||||
if (input.streams)
|
||||
unsubscribeFromStreams(*(input.streams), ctx.session);
|
||||
|
||||
if (input.accounts)
|
||||
unsubscribeFromAccounts(*(input.accounts), ctx.session);
|
||||
|
||||
if (input.accountsProposed)
|
||||
unsubscribeFromProposedAccounts(*(input.accountsProposed), ctx.session);
|
||||
|
||||
if (input.books)
|
||||
unsubscribeFromBooks(*(input.books), ctx.session);
|
||||
|
||||
return Output{};
|
||||
}
|
||||
process(Input input, Context const& ctx) const;
|
||||
|
||||
private:
|
||||
void
|
||||
unsubscribeFromStreams(std::vector<std::string> const& streams, std::shared_ptr<web::ConnectionBase> const& session)
|
||||
const
|
||||
{
|
||||
for (auto const& stream : streams) {
|
||||
if (stream == "ledger") {
|
||||
subscriptions_->unsubLedger(session);
|
||||
} else if (stream == "transactions") {
|
||||
subscriptions_->unsubTransactions(session);
|
||||
} else if (stream == "transactions_proposed") {
|
||||
subscriptions_->unsubProposedTransactions(session);
|
||||
} else if (stream == "validations") {
|
||||
subscriptions_->unsubValidation(session);
|
||||
} else if (stream == "manifests") {
|
||||
subscriptions_->unsubManifest(session);
|
||||
} else if (stream == "book_changes") {
|
||||
subscriptions_->unsubBookChanges(session);
|
||||
} else {
|
||||
ASSERT(false, "Unknown stream: {}", stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
const;
|
||||
|
||||
void
|
||||
unsubscribeFromAccounts(std::vector<std::string> accounts, std::shared_ptr<web::ConnectionBase> const& session)
|
||||
const
|
||||
{
|
||||
for (auto const& account : accounts) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->unsubAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
const;
|
||||
|
||||
void
|
||||
unsubscribeFromProposedAccounts(
|
||||
std::vector<std::string> accountsProposed,
|
||||
std::shared_ptr<web::ConnectionBase> const& session
|
||||
) const
|
||||
{
|
||||
for (auto const& account : accountsProposed) {
|
||||
auto const accountID = accountFromStringStrict(account);
|
||||
subscriptions_->unsubProposedAccount(*accountID, session);
|
||||
}
|
||||
}
|
||||
) const;
|
||||
|
||||
void
|
||||
unsubscribeFromBooks(std::vector<OrderBook> const& books, std::shared_ptr<web::ConnectionBase> const& session) const
|
||||
{
|
||||
for (auto const& orderBook : books) {
|
||||
subscriptions_->unsubBook(orderBook.book, session);
|
||||
|
||||
if (orderBook.both)
|
||||
subscriptions_->unsubBook(ripple::reversed(orderBook.book), session);
|
||||
}
|
||||
}
|
||||
unsubscribeFromBooks(std::vector<OrderBook> const& books, std::shared_ptr<web::ConnectionBase> const& session)
|
||||
const;
|
||||
|
||||
/**
|
||||
* @brief Convert a JSON object to an Input
|
||||
*
|
||||
* @param jv The JSON object to convert
|
||||
* @return The Input object
|
||||
*/
|
||||
friend Input
|
||||
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
|
||||
{
|
||||
auto input = Input{};
|
||||
auto const& jsonObject = jv.as_object();
|
||||
|
||||
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(boost::json::value_to<std::string>(stream));
|
||||
}
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
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(boost::json::value_to<std::string>(account));
|
||||
}
|
||||
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();
|
||||
|
||||
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
|
||||
internalBook.both = both->value().as_bool();
|
||||
|
||||
auto const parsedBookMaybe = parseBook(book.as_object());
|
||||
internalBook.book = std::get<ripple::Book>(parsedBookMaybe);
|
||||
input.books->push_back(internalBook);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The unsubscribe command tells the server to stop sending messages for a particular subscription or set of
|
||||
* subscriptions.
|
||||
*
|
||||
* For more details see: https://xrpl.org/unsubscribe.html
|
||||
*/
|
||||
using UnsubscribeHandler = BaseUnsubscribeHandler<feed::SubscriptionManager>;
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
Reference in New Issue
Block a user