monero-lws/src/server_main.cpp

242 lines
9.2 KiB
C++
Raw Normal View History

// Copyright (c) 2018-2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/filesystem/operations.hpp>
#include <boost/optional/optional.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/thread/thread.hpp>
#include <chrono>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "common/command_line.h" // monero/src/
#include "common/expect.h" // monero/src/
#include "common/util.h" // monero/src/
#include "config.h"
#include "cryptonote_config.h" // monero/src/
#include "db/storage.h"
#include "options.h"
#include "rest_server.h"
#include "scanner.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "lws"
namespace
{
struct options : lws::options
{
const command_line::arg_descriptor<std::string> daemon_rpc;
const command_line::arg_descriptor<std::vector<std::string>> rest_servers;
const command_line::arg_descriptor<std::string> rest_ssl_key;
const command_line::arg_descriptor<std::string> rest_ssl_cert;
const command_line::arg_descriptor<std::size_t> rest_threads;
const command_line::arg_descriptor<std::size_t> scan_threads;
const command_line::arg_descriptor<std::vector<std::string>> access_controls;
const command_line::arg_descriptor<bool> external_bind;
const command_line::arg_descriptor<unsigned> create_queue_max;
const command_line::arg_descriptor<std::chrono::minutes::rep> rates_interval;
const command_line::arg_descriptor<unsigned short> log_level;
static std::string get_default_zmq()
{
static constexpr const char base[] = "tcp://127.0.0.1:";
switch (lws::config::network)
{
case cryptonote::TESTNET:
return base + std::to_string(config::testnet::ZMQ_RPC_DEFAULT_PORT);
case cryptonote::STAGENET:
return base + std::to_string(config::stagenet::ZMQ_RPC_DEFAULT_PORT);
case cryptonote::MAINNET:
default:
break;
}
return base + std::to_string(config::ZMQ_RPC_DEFAULT_PORT);
}
options()
: lws::options()
, daemon_rpc{"daemon", "<protocol>://<address>:<port> of a monerod ZMQ RPC", get_default_zmq()}
, rest_servers{"rest-server", "[(https|http)://<address>:]<port> for incoming connections, multiple declarations allowed"}
, rest_ssl_key{"rest-ssl-key", "<path> to PEM formatted SSL key for https REST server", ""}
, rest_ssl_cert{"rest-ssl-certificate", "<path> to PEM formatted SSL certificate (chains supported) for https REST server", ""}
, rest_threads{"rest-threads", "Number of threads to process REST connections", 1}
, scan_threads{"scan-threads", "Maximum number of threads for account scanning", boost::thread::hardware_concurrency()}
, access_controls{"access-control-origin", "Specify a whitelisted HTTP control origin domain"}
, external_bind{"confirm-external-bind", "Allow listening for external connections", false}
, create_queue_max{"create-queue-max", "Set pending create account requests maximum", 10000}
, rates_interval{"exchange-rate-interval", "Retrieve exchange rates in minute intervals from cryptocompare.com if greater than 0", 0}
, log_level{"log-level", "Log level [0-4]", 1}
{}
void prepare(boost::program_options::options_description& description) const
{
static constexpr const char rest_default[] = "https://0.0.0.0:8443";
lws::options::prepare(description);
command_line::add_arg(description, daemon_rpc);
description.add_options()(rest_servers.name, boost::program_options::value<std::vector<std::string>>()->default_value({rest_default}, rest_default), rest_servers.description);
command_line::add_arg(description, rest_ssl_key);
command_line::add_arg(description, rest_ssl_cert);
command_line::add_arg(description, rest_threads);
command_line::add_arg(description, scan_threads);
command_line::add_arg(description, access_controls);
command_line::add_arg(description, external_bind);
command_line::add_arg(description, create_queue_max);
command_line::add_arg(description, rates_interval);
command_line::add_arg(description, log_level);
}
};
struct program
{
std::string db_path;
std::vector<std::string> rest_servers;
lws::rest_server::configuration rest_config;
std::string daemon_rpc;
std::chrono::minutes rates_interval;
std::size_t scan_threads;
unsigned create_queue_max;
};
void print_help(std::ostream& out)
{
boost::program_options::options_description description{"Options"};
options{}.prepare(description);
out << "Usage: [options]" << std::endl;
out << description;
}
boost::optional<program> get_program(int argc, char** argv)
{
namespace po = boost::program_options;
const options opts{};
po::variables_map args{};
{
po::options_description description{"Options"};
opts.prepare(description);
po::store(
po::command_line_parser(argc, argv).options(description).run(), args
);
po::notify(args);
}
if (command_line::get_arg(args, command_line::arg_help))
{
print_help(std::cout);
return boost::none;
}
opts.set_network(args); // do this first, sets global variable :/
mlog_set_log_level(command_line::get_arg(args, opts.log_level));
program prog{
command_line::get_arg(args, opts.db_path),
command_line::get_arg(args, opts.rest_servers),
lws::rest_server::configuration{
{command_line::get_arg(args, opts.rest_ssl_key), command_line::get_arg(args, opts.rest_ssl_cert)},
command_line::get_arg(args, opts.access_controls),
command_line::get_arg(args, opts.rest_threads),
command_line::get_arg(args, opts.external_bind)
},
command_line::get_arg(args, opts.daemon_rpc),
std::chrono::minutes{command_line::get_arg(args, opts.rates_interval)},
command_line::get_arg(args, opts.scan_threads),
command_line::get_arg(args, opts.create_queue_max),
};
prog.rest_config.threads = std::max(std::size_t(1), prog.rest_config.threads);
prog.scan_threads = std::max(std::size_t(1), prog.scan_threads);
if (command_line::is_arg_defaulted(args, opts.daemon_rpc))
prog.daemon_rpc = options::get_default_zmq();
return prog;
}
void run(program prog)
{
std::signal(SIGINT, [] (int) { lws::scanner::stop(); });
boost::filesystem::create_directories(prog.db_path);
auto disk = lws::db::storage::open(prog.db_path.c_str(), prog.create_queue_max);
auto ctx = lws::rpc::context::make(std::move(prog.daemon_rpc), prog.rates_interval);
MINFO("Using monerod ZMQ RPC at " << ctx.daemon_address());
auto client = lws::scanner::sync(disk.clone(), ctx.connect().value()).value();
lws::rest_server server{epee::to_span(prog.rest_servers), disk.clone(), std::move(client), std::move(prog.rest_config)};
for (const std::string& address : prog.rest_servers)
MINFO("Listening for REST clients at " << address);
// blocks until SIGINT
lws::scanner::run(std::move(disk), std::move(ctx), prog.scan_threads);
}
} // anonymous
int main(int argc, char** argv)
{
tools::on_startup(); // if it throws, don't use MERROR just print default msg
try
{
boost::optional<program> prog;
try
{
prog = get_program(argc, argv);
}
catch (std::exception const& e)
{
std::cerr << e.what() << std::endl << std::endl;
print_help(std::cerr);
return EXIT_FAILURE;
}
if (prog)
run(std::move(*prog));
}
catch (std::exception const& e)
{
MERROR(e.what());
return EXIT_FAILURE;
}
catch (...)
{
MERROR("Unknown exception");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}