refactor: Refactor main (#1555)

For #442.
This commit is contained in:
Sergey Kuznetsov
2024-08-01 10:53:17 +01:00
committed by GitHub
parent fb90fb27ae
commit 2fef03d766
36 changed files with 920 additions and 376 deletions

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022-2023, the clio developers.
Copyright (c) 2022-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
@@ -17,234 +17,44 @@
*/
//==============================================================================
#include "data/AmendmentCenter.hpp"
#include "data/BackendFactory.hpp"
#include "etl/ETLService.hpp"
#include "etl/NetworkValidatedLedgers.hpp"
#include "feed/SubscriptionManager.hpp"
#include "main/Build.hpp"
#include "rpc/Counters.hpp"
#include "rpc/RPCEngine.hpp"
#include "rpc/WorkQueue.hpp"
#include "app/CliArgs.hpp"
#include "app/ClioApplication.hpp"
#include "rpc/common/impl/HandlerProvider.hpp"
#include "util/SignalsHandler.hpp"
#include "util/TerminationHandler.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/Server.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/variables_map.hpp>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <fstream>
#include <functional>
#include <ios>
#include <iostream>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
using namespace util;
using namespace boost::asio;
namespace po = boost::program_options;
/**
* @brief Parse command line and return path to configuration file
*
* @param argc
* @param argv
* @return Path to configuration file
*/
std::string
parseCli(int argc, char* argv[])
{
static constexpr char defaultConfigPath[] = "/etc/opt/clio/config.json";
// clang-format off
po::options_description description("Options");
description.add_options()
("help,h", "print help message and exit")
("version,v", "print version and exit")
("conf,c", po::value<std::string>()->default_value(defaultConfigPath), "configuration file")
;
// clang-format on
po::positional_options_description positional;
positional.add("conf", 1);
po::variables_map parsed;
po::store(po::command_line_parser(argc, argv).options(description).positional(positional).run(), parsed);
po::notify(parsed);
if (parsed.count("version") != 0u) {
std::cout << Build::getClioFullVersionString() << '\n';
std::exit(EXIT_SUCCESS);
}
if (parsed.count("help") != 0u) {
std::cout << "Clio server " << Build::getClioFullVersionString() << "\n\n" << description;
std::exit(EXIT_SUCCESS);
}
return parsed["conf"].as<std::string>();
}
/**
* @brief Parse certificates from configuration file
*
* @param config The configuration
* @return SSL context if certificates were parsed
*/
std::optional<ssl::context>
parseCerts(Config const& config)
{
if (!config.contains("ssl_cert_file") || !config.contains("ssl_key_file"))
return {};
auto certFilename = config.value<std::string>("ssl_cert_file");
auto keyFilename = config.value<std::string>("ssl_key_file");
std::ifstream const readCert(certFilename, std::ios::in | std::ios::binary);
if (!readCert)
return {};
std::stringstream contents;
contents << readCert.rdbuf();
std::string cert = contents.str();
std::ifstream readKey(keyFilename, std::ios::in | std::ios::binary);
if (!readKey)
return {};
contents.str("");
contents << readKey.rdbuf();
readKey.close();
std::string key = contents.str();
ssl::context ctx{ssl::context::tls_server};
ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
ctx.use_certificate_chain(buffer(cert.data(), cert.size()));
ctx.use_private_key(buffer(key.data(), key.size()), ssl::context::file_format::pem);
return ctx;
}
/**
* @brief Start context threads
*
* @param ioc Context
* @param numThreads Number of worker threads to start
*/
void
start(io_context& ioc, std::uint32_t numThreads)
{
std::vector<std::thread> v;
v.reserve(numThreads - 1);
for (auto i = numThreads - 1; i > 0; --i)
v.emplace_back([&ioc] { ioc.run(); });
ioc.run();
for (auto& t : v)
t.join();
}
int
main(int argc, char* argv[])
main(int argc, char const* argv[])
try {
util::setTerminationHandler();
auto const configPath = parseCli(argc, argv);
auto const config = ConfigReader::open(configPath);
if (!config) {
std::cerr << "Couldnt parse config '" << configPath << "'." << std::endl;
return EXIT_FAILURE;
}
util::SignalsHandler signalsHandler{config};
LogService::init(config);
LOG(LogService::info()) << "Clio version: " << Build::getClioFullVersionString();
PrometheusService::init(config);
auto const threads = config.valueOr("io_threads", 2);
if (threads <= 0) {
LOG(LogService::fatal()) << "io_threads is less than 1";
return EXIT_FAILURE;
}
LOG(LogService::info()) << "Number of io threads = " << threads;
// IO context to handle all incoming requests, as well as other things.
// This is not the only io context in the application.
io_context ioc{threads};
// Rate limiter, to prevent abuse
auto sweepHandler = web::IntervalSweepHandler{config, ioc};
auto whitelistHandler = web::WhitelistHandler{config};
auto dosGuard = web::DOSGuard{config, whitelistHandler, sweepHandler};
// Interface to the database
auto backend = data::make_Backend(config);
// Manages clients subscribed to streams
auto subscriptionsRunner = feed::SubscriptionManagerRunner(config, backend);
auto const subscriptions = subscriptionsRunner.getManager();
// Tracks which ledgers have been validated by the network
auto ledgers = etl::NetworkValidatedLedgers::make_ValidatedLedgers();
// Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data.
// The server uses the balancer to forward RPCs to a rippled node.
// The balancer itself publishes to streams (transactions_proposed and accounts_proposed)
auto balancer = etl::LoadBalancer::make_LoadBalancer(config, ioc, backend, subscriptions, ledgers);
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only publishes
auto etl = etl::ETLService::make_ETLService(config, ioc, backend, subscriptions, balancer, ledgers);
auto workQueue = rpc::WorkQueue::make_WorkQueue(config);
auto counters = rpc::Counters::make_Counters(workQueue);
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
auto const handlerProvider = std::make_shared<rpc::impl::ProductionHandlerProvider const>(
config, backend, subscriptions, balancer, etl, amendmentCenter, counters
auto const action = app::CliArgs::parse(argc, argv);
return action.apply(
[](app::CliArgs::Action::Exit const& exit) { return exit.exitCode; },
[](app::CliArgs::Action::Run const& run) {
auto const config = util::ConfigReader::open(run.configPath);
if (!config) {
std::cerr << "Couldnt parse config '" << run.configPath << "'." << std::endl;
return EXIT_FAILURE;
}
util::LogService::init(config);
app::ClioApplication clio{config};
return clio.run();
}
);
auto const rpcEngine =
rpc::RPCEngine::make_RPCEngine(backend, balancer, dosGuard, workQueue, counters, handlerProvider);
// Init the web server
auto handler =
std::make_shared<web::RPCServerHandler<rpc::RPCEngine, etl::ETLService>>(config, backend, rpcEngine, etl);
auto ctx = parseCerts(config);
auto const ctxRef = ctx ? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()} : std::nullopt;
auto const httpServer = web::make_HttpServer(config, ioc, ctxRef, dosGuard, handler);
// Blocks until stopped.
// When stopped, shared_ptrs fall out of scope
// Calls destructors on all resources, and destructs in order
start(ioc, threads);
return EXIT_SUCCESS;
} catch (std::exception const& e) {
LOG(LogService::fatal()) << "Exit on exception: " << e.what();
LOG(util::LogService::fatal()) << "Exit on exception: " << e.what();
return EXIT_FAILURE;
} catch (...) {
LOG(LogService::fatal()) << "Exit on exception: unknown";
LOG(util::LogService::fatal()) << "Exit on exception: unknown";
return EXIT_FAILURE;
}