Files
rippled/src/xrpld/rpc/detail/Handler.cpp
2026-04-02 23:46:17 +00:00

267 lines
10 KiB
C++

#include <xrpld/rpc/detail/Handler.h>
#include <xrpld/rpc/handlers/Handlers.h>
#include <xrpld/rpc/handlers/server_info/Version.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/ApiVersion.h>
#include <map>
namespace xrpl {
namespace RPC {
namespace {
/** Adjust an old-style handler to be call-by-reference. */
template <typename Function>
Handler::Method<Json::Value>
byRef(Function const& f)
{
return [f](JsonContext& context, Json::Value& result) {
result = f(context);
if (result.type() != Json::objectValue)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::RPC::byRef : result is object");
result = RPC::makeObjectValue(result);
// LCOV_EXCL_STOP
}
return Status();
};
}
template <class Object, class HandlerImpl>
Status
handle(JsonContext& context, Object& object)
{
XRPL_ASSERT(
context.apiVersion >= HandlerImpl::minApiVer &&
context.apiVersion <= HandlerImpl::maxApiVer,
"xrpl::RPC::handle : valid API version");
HandlerImpl handler(context);
auto status = handler.check();
if (status)
{
status.inject(object);
}
else
{
handler.writeResult(object);
}
return status;
}
template <typename HandlerImpl>
Handler
handlerFrom()
{
return {
HandlerImpl::name,
&handle<Json::Value, HandlerImpl>,
HandlerImpl::role,
HandlerImpl::condition,
HandlerImpl::minApiVer,
HandlerImpl::maxApiVer};
}
Handler const handlerArray[]{
// Some handlers not specified here are added to the table via addHandler()
// Request-response methods
{"account_info", byRef(&doAccountInfo), Role::USER, NO_CONDITION},
{"account_currencies", byRef(&doAccountCurrencies), Role::USER, NO_CONDITION},
{"account_lines", byRef(&doAccountLines), Role::USER, NO_CONDITION},
{"account_channels", byRef(&doAccountChannels), Role::USER, NO_CONDITION},
{"account_nfts", byRef(&doAccountNFTs), Role::USER, NO_CONDITION},
{"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION},
{"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION},
{"account_tx", byRef(&doAccountTx), Role::USER, NO_CONDITION},
{"amm_info", byRef(&doAMMInfo), Role::USER, NO_CONDITION},
{"blacklist", byRef(&doBlackList), Role::ADMIN, NO_CONDITION},
{"book_changes", byRef(&doBookChanges), Role::USER, NO_CONDITION},
{"book_offers", byRef(&doBookOffers), Role::USER, NO_CONDITION},
{"can_delete", byRef(&doCanDelete), Role::ADMIN, NO_CONDITION},
{"channel_authorize", byRef(&doChannelAuthorize), Role::USER, NO_CONDITION},
{"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION},
{"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION},
{"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION},
{"deposit_authorized", byRef(&doDepositAuthorized), Role::USER, NO_CONDITION},
{"feature", byRef(&doFeature), Role::USER, NO_CONDITION},
{"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER},
{"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION},
{"gateway_balances", byRef(&doGatewayBalances), Role::USER, NO_CONDITION},
{"get_counts", byRef(&doGetCounts), Role::ADMIN, NO_CONDITION},
{"get_aggregate_price", byRef(&doGetAggregatePrice), Role::USER, NO_CONDITION},
{"ledger_accept", byRef(&doLedgerAccept), Role::ADMIN, NEEDS_CURRENT_LEDGER},
{"ledger_cleaner", byRef(&doLedgerCleaner), Role::ADMIN, NEEDS_NETWORK_CONNECTION},
{"ledger_closed", byRef(&doLedgerClosed), Role::USER, NEEDS_CLOSED_LEDGER},
{"ledger_current", byRef(&doLedgerCurrent), Role::USER, NEEDS_CURRENT_LEDGER},
{"ledger_data", byRef(&doLedgerData), Role::USER, NO_CONDITION},
{"ledger_entry", byRef(&doLedgerEntry), Role::USER, NO_CONDITION},
{"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION, 1, 1},
{"ledger_request", byRef(&doLedgerRequest), Role::ADMIN, NO_CONDITION},
{"log_level", byRef(&doLogLevel), Role::ADMIN, NO_CONDITION},
{"logrotate", byRef(&doLogRotate), Role::ADMIN, NO_CONDITION},
{"manifest", byRef(&doManifest), Role::USER, NO_CONDITION},
{"nft_buy_offers", byRef(&doNFTBuyOffers), Role::USER, NO_CONDITION},
{"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION},
{"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION},
{"owner_info", byRef(&doOwnerInfo), Role::USER, NEEDS_CURRENT_LEDGER},
{"peers", byRef(&doPeers), Role::ADMIN, NO_CONDITION},
{"path_find", byRef(&doPathFind), Role::USER, NEEDS_CURRENT_LEDGER},
{"ping", byRef(&doPing), Role::USER, NO_CONDITION},
{"print", byRef(&doPrint), Role::ADMIN, NO_CONDITION},
// { "profile", byRef (&doProfile), Role::USER,
// NEEDS_CURRENT_LEDGER },
{"random", byRef(&doRandom), Role::USER, NO_CONDITION},
{"peer_reservations_add", byRef(&doPeerReservationsAdd), Role::ADMIN, NO_CONDITION},
{"peer_reservations_del", byRef(&doPeerReservationsDel), Role::ADMIN, NO_CONDITION},
{"peer_reservations_list", byRef(&doPeerReservationsList), Role::ADMIN, NO_CONDITION},
{"ripple_path_find", byRef(&doRipplePathFind), Role::USER, NO_CONDITION},
{"server_definitions", byRef(&doServerDefinitions), Role::USER, NO_CONDITION},
{"server_info", byRef(&doServerInfo), Role::USER, NO_CONDITION},
{"server_state", byRef(&doServerState), Role::USER, NO_CONDITION},
{"sign", byRef(&doSign), Role::USER, NO_CONDITION},
{"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION},
{"simulate", byRef(&doSimulate), Role::USER, NEEDS_CURRENT_LEDGER},
{"stop", byRef(&doStop), Role::ADMIN, NO_CONDITION},
{"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER},
{"submit_multisigned", byRef(&doSubmitMultiSigned), Role::USER, NEEDS_CURRENT_LEDGER},
{"transaction_entry", byRef(&doTransactionEntry), Role::USER, NO_CONDITION},
{"tx", byRef(&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION},
{"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION, 1, 1},
{"tx_reduce_relay", byRef(&doTxReduceRelay), Role::USER, NO_CONDITION},
{"unl_list", byRef(&doUnlList), Role::ADMIN, NO_CONDITION},
{"validation_create", byRef(&doValidationCreate), Role::ADMIN, NO_CONDITION},
{"validators", byRef(&doValidators), Role::ADMIN, NO_CONDITION},
{"validator_list_sites", byRef(&doValidatorListSites), Role::ADMIN, NO_CONDITION},
{"validator_info", byRef(&doValidatorInfo), Role::ADMIN, NO_CONDITION},
{"vault_info", byRef(&doVaultInfo), Role::USER, NO_CONDITION},
{"wallet_propose", byRef(&doWalletPropose), Role::ADMIN, NO_CONDITION},
// Event methods
{"subscribe", byRef(&doSubscribe), Role::USER, NO_CONDITION},
{"unsubscribe", byRef(&doUnsubscribe), Role::USER, NO_CONDITION},
};
class HandlerTable
{
private:
using handler_table_t = std::multimap<std::string, Handler>;
// Use with equal_range to enforce that API range of a newly added handler
// does not overlap with API range of an existing handler with same name
[[nodiscard]] static bool
overlappingApiVersion(
std::pair<handler_table_t::iterator, handler_table_t::iterator> range,
unsigned minVer,
unsigned maxVer)
{
XRPL_ASSERT(minVer <= maxVer, "xrpl::RPC::HandlerTable : valid API version range");
XRPL_ASSERT(
maxVer <= RPC::apiMaximumValidVersion,
"xrpl::RPC::HandlerTable : valid max API version");
return std::any_of(
range.first,
range.second, //
[minVer, maxVer](auto const& item) {
return item.second.minApiVer_ <= maxVer && item.second.maxApiVer_ >= minVer;
});
}
template <std::size_t N>
explicit HandlerTable(Handler const (&entries)[N])
{
for (auto const& entry : entries)
{
if (overlappingApiVersion(
table_.equal_range(entry.name_), entry.minApiVer_, entry.maxApiVer_))
{
LogicError(
std::string("Handler for ") + entry.name_ +
" overlaps with an existing handler");
}
table_.insert({entry.name_, entry});
}
// This is where the new-style handlers are added.
addHandler<LedgerHandler>();
addHandler<VersionHandler>();
}
public:
static HandlerTable const&
instance()
{
static HandlerTable const handlerTable(handlerArray);
return handlerTable;
}
[[nodiscard]] Handler const*
getHandler(unsigned version, bool betaEnabled, std::string const& name) const
{
if (version < RPC::apiMinimumSupportedVersion ||
version > (betaEnabled ? RPC::apiBetaVersion : RPC::apiMaximumSupportedVersion))
return nullptr;
auto const range = table_.equal_range(name);
auto const i = std::find_if(range.first, range.second, [version](auto const& entry) {
return entry.second.minApiVer_ <= version && version <= entry.second.maxApiVer_;
});
return i == range.second ? nullptr : &i->second;
}
[[nodiscard]] std::set<char const*>
getHandlerNames() const
{
std::set<char const*> ret;
for (auto const& i : table_)
ret.insert(i.second.name_);
return ret;
}
private:
handler_table_t table_;
template <class HandlerImpl>
void
addHandler()
{
static_assert(HandlerImpl::minApiVer <= HandlerImpl::maxApiVer);
static_assert(HandlerImpl::maxApiVer <= RPC::apiMaximumValidVersion);
static_assert(RPC::apiMinimumSupportedVersion <= HandlerImpl::minApiVer);
if (overlappingApiVersion(
table_.equal_range(HandlerImpl::name),
HandlerImpl::minApiVer,
HandlerImpl::maxApiVer))
{
LogicError(
std::string("Handler for ") + HandlerImpl::name +
" overlaps with an existing handler");
}
table_.insert({HandlerImpl::name, handlerFrom<HandlerImpl>()});
}
};
} // namespace
Handler const*
getHandler(unsigned version, bool betaEnabled, std::string const& name)
{
return HandlerTable::instance().getHandler(version, betaEnabled, name);
}
std::set<char const*>
getHandlerNames()
{
return HandlerTable::instance().getHandlerNames();
}
} // namespace RPC
} // namespace xrpl