diff --git a/CMakeLists.txt b/CMakeLists.txt index f569c9217..7d117e81c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,17 +75,15 @@ include(Settings) include(PatchNixBinary) # Clio deps -include(deps/libxrpl) include(deps/Boost) -include(deps/OpenSSL) -include(deps/Threads) -include(deps/libfmt) include(deps/cassandra) include(deps/libbacktrace) -include(deps/spdlog) - +include(deps/libfmt) +include(deps/libxrpl) +include(deps/OpenSSL) include(deps/rpcspec) -target_link_libraries(clio_options INTERFACE rpcspec::rpcspec) +include(deps/spdlog) +include(deps/Threads) add_subdirectory(src) add_subdirectory(tests) diff --git a/cmake/deps/rpcspec.cmake b/cmake/deps/rpcspec.cmake index b77e67e00..c3a53dbc7 100644 --- a/cmake/deps/rpcspec.cmake +++ b/cmake/deps/rpcspec.cmake @@ -1,5 +1 @@ find_package(xrpl-rpc-spec REQUIRED CONFIG) - -if(NOT DEFINED RPCSPEC_IS_CLIO) - add_compile_definitions(RPCSPEC_IS_CLIO=1) -endif() diff --git a/conan.lock b/conan.lock index 3c698599d..c0a1fcd70 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.3#e0552b7fdcb26d9249498fa4273474f1%1786463175.210094", + "xrpl-rpc-spec/0.1.6#0147dce06088874791dca62dc8503b6c%1787751048.16847", "xrpl/3.3.0#5e356a24ae1f0d6da6bd617b926f92e6%1786467262.262007", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index 58e2b3b31..3ef0797cf 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.3", + "xrpl-rpc-spec/0.1.6", "xrpl/3.3.0", ] @@ -33,6 +33,7 @@ class ClioConan(ConanFile): "protobuf/*:shared": False, "protobuf/*:with_zlib": True, "snappy/*:shared": False, + "xrpl-rpc-spec/*:server": "clio", "xrpl/*:rocksdb": True, # TODO: revert to false when includes are fixed in libxrpl "xrpl/*:tests": False, } diff --git a/src/etl/CMakeLists.txt b/src/etl/CMakeLists.txt index 17cabe312..89ff92894 100644 --- a/src/etl/CMakeLists.txt +++ b/src/etl/CMakeLists.txt @@ -29,4 +29,4 @@ target_sources( impl/ext/Successor.cpp ) -target_link_libraries(clio_etl PUBLIC clio_data clio_util) +target_link_libraries(clio_etl PUBLIC clio_data clio_util rpcspec::rpcspec) diff --git a/src/feed/CMakeLists.txt b/src/feed/CMakeLists.txt index e136caca8..acfd1361c 100644 --- a/src/feed/CMakeLists.txt +++ b/src/feed/CMakeLists.txt @@ -9,4 +9,4 @@ target_sources( impl/SingleFeedBase.cpp ) -target_link_libraries(clio_feed PRIVATE clio_util) +target_link_libraries(clio_feed PRIVATE clio_util rpcspec::rpcspec) diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 544d40494..73dbcade0 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources( common/MetaProcessors.cpp common/impl/APIVersionParser.cpp common/impl/HandlerProvider.cpp + common/impl/HandlerRegistry.cpp filters/impl/DelegateTransactionsFilter.cpp handlers/AccountChannels.cpp handlers/AccountCurrencies.cpp @@ -60,4 +61,4 @@ target_sources( handlers/VaultInfo.cpp ) -target_link_libraries(clio_rpc PUBLIC clio_util clio_data) +target_link_libraries(clio_rpc PUBLIC clio_util clio_data rpcspec::rpcspec) diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp index c199753c8..3e42c5db9 100644 --- a/src/rpc/common/impl/HandlerProvider.cpp +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -5,47 +5,8 @@ #include "etl/ETLServiceInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "rpc/Counters.hpp" #include "rpc/common/AnyHandler.hpp" -#include "rpc/handlers/AMMInfo.hpp" -#include "rpc/handlers/AccountChannels.hpp" -#include "rpc/handlers/AccountCurrencies.hpp" -#include "rpc/handlers/AccountInfo.hpp" -#include "rpc/handlers/AccountLines.hpp" -#include "rpc/handlers/AccountMPTokenIssuances.hpp" -#include "rpc/handlers/AccountMPTokens.hpp" -#include "rpc/handlers/AccountNFTs.hpp" -#include "rpc/handlers/AccountObjects.hpp" -#include "rpc/handlers/AccountOffers.hpp" -#include "rpc/handlers/AccountTx.hpp" -#include "rpc/handlers/BookChanges.hpp" -#include "rpc/handlers/BookOffers.hpp" -#include "rpc/handlers/DepositAuthorized.hpp" -#include "rpc/handlers/Feature.hpp" -#include "rpc/handlers/GatewayBalances.hpp" -#include "rpc/handlers/GetAggregatePrice.hpp" -#include "rpc/handlers/Ledger.hpp" -#include "rpc/handlers/LedgerData.hpp" -#include "rpc/handlers/LedgerEntry.hpp" -#include "rpc/handlers/LedgerIndex.hpp" -#include "rpc/handlers/LedgerRange.hpp" -#include "rpc/handlers/MPTHolders.hpp" -#include "rpc/handlers/MPTokenIssuanceHistory.hpp" -#include "rpc/handlers/NFTBuyOffers.hpp" -#include "rpc/handlers/NFTHistory.hpp" -#include "rpc/handlers/NFTInfo.hpp" -#include "rpc/handlers/NFTSellOffers.hpp" -#include "rpc/handlers/NFTsByIssuer.hpp" -#include "rpc/handlers/NoRippleCheck.hpp" -#include "rpc/handlers/Ping.hpp" -#include "rpc/handlers/Random.hpp" -#include "rpc/handlers/ServerInfo.hpp" -#include "rpc/handlers/Subscribe.hpp" -#include "rpc/handlers/TransactionEntry.hpp" -#include "rpc/handlers/Tx.hpp" -#include "rpc/handlers/Unsubscribe.hpp" -#include "rpc/handlers/VaultInfo.hpp" -#include "rpc/handlers/VersionHandler.hpp" +#include "rpc/common/impl/HandlerRegistry.hpp" #include "util/config/ConfigDefinition.hpp" #include @@ -64,57 +25,29 @@ ProductionHandlerProvider::ProductionHandlerProvider( std::shared_ptr const& amendmentCenter, Counters const& counters ) - : handlerMap_{ - {"account_channels", {.handler = AccountChannelsHandler{backend}}}, - {"account_currencies", {.handler = AccountCurrenciesHandler{backend}}}, - {"account_info", {.handler = AccountInfoHandler{backend, amendmentCenter}}}, - {"account_lines", {.handler = AccountLinesHandler{backend}}}, - {"account_mptoken_issuances", - {.handler = AccountMPTokenIssuancesHandler{backend}, .isClioOnly = true}}, // clio only - {"account_mptokens", - {.handler = AccountMPTokensHandler{backend}, .isClioOnly = true}}, // clio only - {"account_nfts", {.handler = AccountNFTsHandler{backend}}}, - {"account_objects", {.handler = AccountObjectsHandler{backend}}}, - {"account_offers", {.handler = AccountOffersHandler{backend}}}, - {"account_tx", {.handler = AccountTxHandler{backend, etl}}}, - {"amm_info", {.handler = AMMInfoHandler{backend, amendmentCenter}}}, - {"book_changes", {.handler = BookChangesHandler{backend}}}, - {"book_offers", {.handler = BookOffersHandler{backend, amendmentCenter}}}, - {"deposit_authorized", {.handler = DepositAuthorizedHandler{backend}}}, - {"feature", {.handler = FeatureHandler{backend, amendmentCenter}}}, - {"gateway_balances", {.handler = GatewayBalancesHandler{backend}}}, - {"get_aggregate_price", {.handler = GetAggregatePriceHandler{backend}}}, - {"ledger", {.handler = LedgerHandler{backend, amendmentCenter}}}, - {"ledger_data", {.handler = LedgerDataHandler{backend}}}, - {"ledger_entry", {.handler = LedgerEntryHandler{backend}}}, - {"ledger_index", - {.handler = LedgerIndexHandler{backend}, .isClioOnly = true}}, // clio only - {"ledger_range", {.handler = LedgerRangeHandler{backend}}}, - {"mpt_holders", - {.handler = MPTHoldersHandler{backend}, .isClioOnly = true}}, // clio only - {"mptoken_issuance_history", - {.handler = MPTokenIssuanceHistoryHandler{backend}, .isClioOnly = true}}, // clio only - {"nfts_by_issuer", - {.handler = NFTsByIssuerHandler{backend}, .isClioOnly = true}}, // clio only - {"nft_history", - {.handler = NFTHistoryHandler{backend}, .isClioOnly = true}}, // clio only - {"nft_buy_offers", {.handler = NFTBuyOffersHandler{backend}}}, - {"nft_info", {.handler = NFTInfoHandler{backend}, .isClioOnly = true}}, // clio only - {"nft_sell_offers", {.handler = NFTSellOffersHandler{backend}}}, - {"noripple_check", {.handler = NoRippleCheckHandler{backend}}}, - {"ping", {.handler = PingHandler{}}}, - {"random", {.handler = RandomHandler{}}}, - {"server_info", - {.handler = ServerInfoHandler{backend, subscriptionManager, balancer, etl, counters}}}, - {"transaction_entry", {.handler = TransactionEntryHandler{backend}}}, - {"tx", {.handler = TxHandler{backend, etl}}}, - {"subscribe", - {.handler = SubscribeHandler{backend, amendmentCenter, subscriptionManager}}}, - {"unsubscribe", {.handler = UnsubscribeHandler{subscriptionManager}}}, - {"vault_info", {.handler = VaultInfoHandler{backend}}}, - {"version", {.handler = VersionHandler{config}}}, - } { + HandlerDeps const deps{ + .config = config, + .backend = backend, + .subscriptionManager = subscriptionManager, + .balancer = balancer, + .etl = etl, + .amendmentCenter = amendmentCenter, + .counters = counters + }; + + auto const registry = handlerRegistry(); + handlerMap_.reserve(registry.size()); + + for (auto const& entry : registry) { + handlerMap_.emplace( + entry.name, + Handler{ + .handler = entry.factory(deps), + .isClioOnly = entry.isClioOnly, + } + ); + } } bool diff --git a/src/rpc/common/impl/HandlerRegistry.cpp b/src/rpc/common/impl/HandlerRegistry.cpp new file mode 100644 index 000000000..755e0d0c5 --- /dev/null +++ b/src/rpc/common/impl/HandlerRegistry.cpp @@ -0,0 +1,328 @@ +#include "rpc/common/impl/HandlerRegistry.hpp" + +#include "rpc/Counters.hpp" // IWYU pragma: keep +#include "rpc/common/AnyHandler.hpp" +#include "rpc/handlers/AMMInfo.hpp" +#include "rpc/handlers/AccountChannels.hpp" +#include "rpc/handlers/AccountCurrencies.hpp" +#include "rpc/handlers/AccountInfo.hpp" +#include "rpc/handlers/AccountLines.hpp" +#include "rpc/handlers/AccountMPTokenIssuances.hpp" +#include "rpc/handlers/AccountMPTokens.hpp" +#include "rpc/handlers/AccountNFTs.hpp" +#include "rpc/handlers/AccountObjects.hpp" +#include "rpc/handlers/AccountOffers.hpp" +#include "rpc/handlers/AccountTx.hpp" +#include "rpc/handlers/BookChanges.hpp" +#include "rpc/handlers/BookOffers.hpp" +#include "rpc/handlers/DepositAuthorized.hpp" +#include "rpc/handlers/Feature.hpp" +#include "rpc/handlers/GatewayBalances.hpp" +#include "rpc/handlers/GetAggregatePrice.hpp" +#include "rpc/handlers/Ledger.hpp" +#include "rpc/handlers/LedgerData.hpp" +#include "rpc/handlers/LedgerEntry.hpp" +#include "rpc/handlers/LedgerIndex.hpp" +#include "rpc/handlers/LedgerRange.hpp" +#include "rpc/handlers/MPTHolders.hpp" +#include "rpc/handlers/MPTokenIssuanceHistory.hpp" +#include "rpc/handlers/NFTBuyOffers.hpp" +#include "rpc/handlers/NFTHistory.hpp" +#include "rpc/handlers/NFTInfo.hpp" +#include "rpc/handlers/NFTSellOffers.hpp" +#include "rpc/handlers/NFTsByIssuer.hpp" +#include "rpc/handlers/NoRippleCheck.hpp" +#include "rpc/handlers/Ping.hpp" +#include "rpc/handlers/Random.hpp" +#include "rpc/handlers/ServerInfo.hpp" +#include "rpc/handlers/Subscribe.hpp" +#include "rpc/handlers/TransactionEntry.hpp" +#include "rpc/handlers/Tx.hpp" +#include "rpc/handlers/Unsubscribe.hpp" +#include "rpc/handlers/VaultInfo.hpp" +#include "rpc/handlers/VersionHandler.hpp" +#include "util/Concepts.hpp" + +#include +#include +#include + +namespace rpc::impl { + +namespace { + +constexpr auto kHandlers = std::to_array({ + { + .name = "account_channels", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountChannelsHandler{d.backend}; + }, + }, + + { + .name = "account_currencies", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountCurrenciesHandler{d.backend}; + }, + }, + + { + .name = "account_info", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountInfoHandler{d.backend, d.amendmentCenter}; + }, + }, + + { + .name = "account_lines", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountLinesHandler{d.backend}; + }, + }, + + { + .name = "account_mptoken_issuances", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountMPTokenIssuancesHandler{d.backend}; + }, + .isClioOnly = true, + }, + + { + .name = "account_mptokens", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountMPTokensHandler{d.backend}; + }, + .isClioOnly = true, + }, + + { + .name = "account_nfts", + .factory = [](HandlerDeps const& d) -> AnyHandler { return AccountNFTsHandler{d.backend}; }, + }, + + { + .name = "account_objects", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountObjectsHandler{d.backend}; + }, + }, + + { + .name = "account_offers", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountOffersHandler{d.backend}; + }, + }, + + { + .name = "account_tx", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AccountTxHandler{d.backend, d.etl}; + }, + }, + + { + .name = "amm_info", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return AMMInfoHandler{d.backend, d.amendmentCenter}; + }, + }, + + { + .name = "book_changes", + .factory = [](HandlerDeps const& d) -> AnyHandler { return BookChangesHandler{d.backend}; }, + }, + + { + .name = "book_offers", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return BookOffersHandler{d.backend, d.amendmentCenter}; + }, + }, + + { + .name = "deposit_authorized", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return DepositAuthorizedHandler{d.backend}; + }, + }, + + { + .name = "feature", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return FeatureHandler{d.backend, d.amendmentCenter}; + }, + }, + + { + .name = "gateway_balances", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return GatewayBalancesHandler{d.backend}; + }, + }, + + { + .name = "get_aggregate_price", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return GetAggregatePriceHandler{d.backend}; + }, + }, + + { + .name = "ledger", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return LedgerHandler{d.backend, d.amendmentCenter}; + }, + }, + + { + .name = "ledger_data", + .factory = [](HandlerDeps const& d) -> AnyHandler { return LedgerDataHandler{d.backend}; }, + }, + + { + .name = "ledger_entry", + .factory = [](HandlerDeps const& d) -> AnyHandler { return LedgerEntryHandler{d.backend}; }, + }, + + { + .name = "ledger_index", + .factory = [](HandlerDeps const& d) -> AnyHandler { return LedgerIndexHandler{d.backend}; }, + .isClioOnly = true, + }, + + { + .name = "ledger_range", + .factory = [](HandlerDeps const& d) -> AnyHandler { return LedgerRangeHandler{d.backend}; }, + }, + + { + .name = "mpt_holders", + .factory = [](HandlerDeps const& d) -> AnyHandler { return MPTHoldersHandler{d.backend}; }, + .isClioOnly = true, + }, + + { + .name = "mptoken_issuance_history", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return MPTokenIssuanceHistoryHandler{d.backend}; + }, + .isClioOnly = true, + }, + + { + .name = "nfts_by_issuer", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return NFTsByIssuerHandler{d.backend}; + }, + .isClioOnly = true, + }, + + { + .name = "nft_history", + .factory = [](HandlerDeps const& d) -> AnyHandler { return NFTHistoryHandler{d.backend}; }, + .isClioOnly = true, + }, + + { + .name = "nft_buy_offers", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return NFTBuyOffersHandler{d.backend}; + }, + }, + + { + .name = "nft_info", + .factory = [](HandlerDeps const& d) -> AnyHandler { return NFTInfoHandler{d.backend}; }, + .isClioOnly = true, + }, + + { + .name = "nft_sell_offers", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return NFTSellOffersHandler{d.backend}; + }, + }, + + { + .name = "noripple_check", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return NoRippleCheckHandler{d.backend}; + }, + }, + + { + .name = "ping", + .factory = [](HandlerDeps const&) -> AnyHandler { return PingHandler{}; }, + }, + + { + .name = "random", + .factory = [](HandlerDeps const&) -> AnyHandler { return RandomHandler{}; }, + }, + + { + .name = "server_info", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return ServerInfoHandler{ + d.backend, d.subscriptionManager, d.balancer, d.etl, d.counters + }; + }, + }, + + { + .name = "transaction_entry", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return TransactionEntryHandler{d.backend}; + }, + }, + + { + .name = "tx", + .factory = [](HandlerDeps const& d) -> AnyHandler { return TxHandler{d.backend, d.etl}; }, + }, + + { + .name = "subscribe", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return SubscribeHandler{d.backend, d.amendmentCenter, d.subscriptionManager}; + }, + }, + + { + .name = "unsubscribe", + .factory = [](HandlerDeps const& d) -> AnyHandler { + return UnsubscribeHandler{d.subscriptionManager}; + }, + }, + + { + .name = "vault_info", + .factory = [](HandlerDeps const& d) -> AnyHandler { return VaultInfoHandler{d.backend}; }, + }, + + { + .name = "version", + .factory = [](HandlerDeps const& d) -> AnyHandler { return VersionHandler{d.config}; }, + }, +}); + +// A duplicate name would silently shadow a handler so we check at compile time. +static_assert( + std::apply( + [](auto const&... entry) { return util::hasNoDuplicates(entry.name...); }, + kHandlers + ), + "RPC handler names must be unique" +); + +} // namespace + +std::span +handlerRegistry() noexcept +{ + return kHandlers; +} + +} // namespace rpc::impl diff --git a/src/rpc/common/impl/HandlerRegistry.hpp b/src/rpc/common/impl/HandlerRegistry.hpp new file mode 100644 index 000000000..0b08b3c30 --- /dev/null +++ b/src/rpc/common/impl/HandlerRegistry.hpp @@ -0,0 +1,79 @@ +/** @file */ +#pragma once + +#include "rpc/common/AnyHandler.hpp" + +#include +#include +#include + +namespace data { +class AmendmentCenterInterface; +class BackendInterface; +} // namespace data + +namespace etl { +struct ETLServiceInterface; +class LoadBalancerInterface; +} // namespace etl + +namespace feed { +class SubscriptionManagerInterface; +} // namespace feed + +namespace util::config { +class ClioConfigDefinition; +} // namespace util::config + +namespace rpc { +class Counters; +} // namespace rpc + +namespace rpc::impl { + +/** + * @brief The bundle of runtime dependencies a handler factory may consume. + * + * A short-lived view over the caller's objects: it holds references only, so it must + * not outlive the call it is passed to. + */ +struct HandlerDeps { + util::config::ClioConfigDefinition const& config; + std::shared_ptr const& backend; + std::shared_ptr const& subscriptionManager; + std::shared_ptr const& balancer; + std::shared_ptr const& etl; + std::shared_ptr const& amendmentCenter; + Counters const& counters; +}; + +/** + * @brief Constructs one type-erased handler from the available dependencies. + * + * A plain function pointer rather than @c std::function so that the registry stays a + * literal type and is constant-initialised. + */ +using HandlerFactory = AnyHandler (*)(HandlerDeps const&); + +/** + * @brief One row of the handler registry: the method name, how to construct the + * handler, and its static metadata. + */ +struct HandlerEntry { + std::string_view name; + HandlerFactory factory; + bool isClioOnly = false; +}; + +/** + * @brief The full set of registered RPC handlers. + * + * Static data: reading the registry constructs no handler and requires no runtime + * dependencies. + * + * @return A span over the registry, valid for the lifetime of the program + */ +[[nodiscard]] std::span +handlerRegistry() noexcept; + +} // namespace rpc::impl diff --git a/src/web/CMakeLists.txt b/src/web/CMakeLists.txt index facc5e258..2a0db40a6 100644 --- a/src/web/CMakeLists.txt +++ b/src/web/CMakeLists.txt @@ -21,4 +21,4 @@ target_sources( SubscriptionContext.cpp ) -target_link_libraries(clio_web PUBLIC clio_util) +target_link_libraries(clio_web PUBLIC clio_util rpcspec::rpcspec)