Files
wownero-lws/src/server_main.cpp
2025-12-18 10:19:43 -05:00

392 lines
17 KiB
C++

// 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 "error.h"
#include "lws_version.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::string> daemon_sub;
const command_line::arg_descriptor<std::string> zmq_pub;
#ifdef MLWS_RMQ_ENABLED
const command_line::arg_descriptor<std::string> rmq_address;
const command_line::arg_descriptor<std::string> rmq_credentials;
const command_line::arg_descriptor<std::string> rmq_exchange;
const command_line::arg_descriptor<std::string> rmq_routing;
#endif
const command_line::arg_descriptor<std::vector<std::string>> rest_servers;
const command_line::arg_descriptor<std::vector<std::string>> admin_rest_servers;
const command_line::arg_descriptor<std::string> lws_server_addr;
const command_line::arg_descriptor<std::string> lws_server_pass;
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;
const command_line::arg_descriptor<bool> disable_admin_auth;
const command_line::arg_descriptor<std::string> webhook_ssl_verification;
const command_line::arg_descriptor<std::string> config_file;
const command_line::arg_descriptor<std::uint32_t> max_subaddresses;
const command_line::arg_descriptor<bool> auto_accept_creation;
const command_line::arg_descriptor<bool> untrusted_daemon;
const command_line::arg_descriptor<bool> regtest;
const command_line::arg_descriptor<bool> version;
const command_line::arg_descriptor<bool> auto_accept_import;
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()}
, daemon_sub{"sub", "tcp://address:port or ipc://path of a monerod ZMQ Pub", ""}
, zmq_pub{"zmq-pub", "tcp://address:port or ipc://path of a bind location for ZMQ pub events", ""}
#ifdef MLWS_RMQ_ENABLED
, rmq_address{"rmq-address", "tcp://<host>/[vhost]"}
, rmq_credentials{"rmq-credentials", "<user>:<pass>"}
, rmq_exchange{"rmq-exchange", "Name of the RMQ exchange"}
, rmq_routing{"rmq-routing", "Routing for the specified exchange"}
#endif
, rest_servers{"rest-server", "[(https|http)://<address>:]<port>[/<prefix>] for incoming connections, multiple declarations allowed"}
, admin_rest_servers{"admin-rest-server", "[(https|http])://<address>:]<port>[/<prefix>] for incoming admin connections, multiple declarations allowed"}
, lws_server_addr{"lws-server-addr", "[<ip>:]<port> to listen for lws-clients", ""}
, lws_server_pass{"lws-server-pass", "Password for lws-clients connecting to server", ""}
, 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}
, disable_admin_auth{"disable-admin-auth", "Make auth field optional in HTTP-REST requests", false}
, webhook_ssl_verification{"webhook-ssl-verification", "[<none|system_ca>] specify SSL verification mode for webhooks", "system_ca"}
, config_file{"config-file", "Specify any option in a config file; <name>=<value> on separate lines"}
, max_subaddresses{"max-subaddresses", "Maximum number of subaddresses per primary account (defaults to 0)", 0}
, auto_accept_creation{"auto-accept-creation", "New account creation requests are automatically accepted", false}
, untrusted_daemon{"untrusted-daemon", "Perform (expensive) chain-verification and PoW checks", false}
, regtest{"regtest", "Run in a regression testing mode", false}
, version{"version", "Display version and quit", false}
, auto_accept_import{"auto-accept-import", "Account import requests are automatically accepted", false}
{}
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);
command_line::add_arg(description, daemon_sub);
command_line::add_arg(description, lws_server_addr);
command_line::add_arg(description, lws_server_pass);
command_line::add_arg(description, zmq_pub);
#ifdef MLWS_RMQ_ENABLED
command_line::add_arg(description, rmq_address);
command_line::add_arg(description, rmq_credentials);
command_line::add_arg(description, rmq_exchange);
command_line::add_arg(description, rmq_routing);
#endif
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, admin_rest_servers);
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);
command_line::add_arg(description, disable_admin_auth);
command_line::add_arg(description, webhook_ssl_verification);
command_line::add_arg(description, config_file);
command_line::add_arg(description, max_subaddresses);
command_line::add_arg(description, auto_accept_creation);
command_line::add_arg(description, untrusted_daemon);
command_line::add_arg(description, regtest);
command_line::add_arg(description, version);
command_line::add_arg(description, auto_accept_import);
}
};
struct program
{
std::string db_path;
std::vector<std::string> rest_servers;
std::vector<std::string> admin_rest_servers;
std::string lws_server_addr;
std::string lws_server_pass;
lws::rest_server::configuration rest_config;
std::string daemon_rpc;
std::string daemon_sub;
std::string zmq_pub;
lws::rpc::rmq_details rmq;
std::string webhook_ssl_verification;
std::chrono::minutes rates_interval;
std::size_t scan_threads;
unsigned create_queue_max;
bool untrusted_daemon;
bool regtest;
};
void print_version(std::ostream& out)
{
std::cout << lws::version::name << " version " << lws::version::id << " commit " << lws::version::commit << std::endl;;
}
void print_help(std::ostream& out)
{
boost::program_options::options_description description{"Options"};
options{}.prepare(description);
print_version(out);
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::is_arg_defaulted(args, opts.config_file))
{
boost::filesystem::path config_path{command_line::get_arg(args, opts.config_file)};
if (!boost::filesystem::exists(config_path))
MONERO_THROW(lws::error::configuration, "Config file does not exist");
po::store(
po::parse_config_file<char>(config_path.string<std::string>().c_str(), description), args
);
po::notify(args);
}
}
if (command_line::get_arg(args, command_line::arg_help))
{
print_help(std::cout);
return boost::none;
}
if (command_line::get_arg(args, opts.version))
{
print_version(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));
const auto webhook_verify_raw = command_line::get_arg(args, opts.webhook_ssl_verification);
epee::net_utils::ssl_verification_t webhook_verify = epee::net_utils::ssl_verification_t::none;
if (webhook_verify_raw == "system_ca")
webhook_verify = epee::net_utils::ssl_verification_t::system_ca;
else if (webhook_verify_raw != "none")
MONERO_THROW(lws::error::configuration, "Invalid webhook ssl verification mode");
program prog{
command_line::get_arg(args, opts.db_path),
command_line::get_arg(args, opts.rest_servers),
command_line::get_arg(args, opts.admin_rest_servers),
command_line::get_arg(args, opts.lws_server_addr),
command_line::get_arg(args, opts.lws_server_pass),
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.max_subaddresses),
webhook_verify,
command_line::get_arg(args, opts.external_bind),
command_line::get_arg(args, opts.disable_admin_auth),
command_line::get_arg(args, opts.auto_accept_creation),
command_line::get_arg(args, opts.auto_accept_import)
},
command_line::get_arg(args, opts.daemon_rpc),
command_line::get_arg(args, opts.daemon_sub),
command_line::get_arg(args, opts.zmq_pub),
#ifdef MLWS_RMQ_ENABLED
lws::rpc::rmq_details{
command_line::get_arg(args, opts.rmq_address),
command_line::get_arg(args, opts.rmq_credentials),
command_line::get_arg(args, opts.rmq_exchange),
command_line::get_arg(args, opts.rmq_routing)
},
#else
lws::rpc::rmq_details{},
#endif
command_line::get_arg(args, opts.webhook_ssl_verification),
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),
command_line::get_arg(args, opts.untrusted_daemon),
command_line::get_arg(args, opts.regtest)
};
if (prog.regtest && lws::config::network != cryptonote::MAINNET)
MONERO_THROW(lws::error::configuration, "Regtest cannot be used with testnet or stagenet");
if (!prog.lws_server_addr.empty() && (prog.rest_config.max_subaddresses || prog.untrusted_daemon))
MONERO_THROW(lws::error::configuration, "Remote scanning cannot be used with subaddresses or untrusted daemon");
prog.rest_config.threads = std::max(std::size_t(1), prog.rest_config.threads);
if (prog.lws_server_addr.empty())
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)
{
MINFO(lws::version::name << " version " << lws::version::id << " commit " << lws::version::commit);
auto sub_address = prog.daemon_sub;
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), std::move(prog.daemon_sub), std::move(prog.zmq_pub), std::move(prog.rmq), prog.rates_interval, prog.untrusted_daemon);
//! SIGINT handle registered by `scanner` constructor
lws::scanner scanner{disk.clone(), prog.rest_config.webhook_verify};
MINFO("Using monerod ZMQ RPC at " << ctx.daemon_address());
if (!sub_address.empty())
MINFO("Using monerod ZMQ sub at " << sub_address);
auto client = scanner.sync(ctx.connect().value(), prog.untrusted_daemon).value();
lws::rest_server server{
epee::to_span(prog.rest_servers), prog.admin_rest_servers, std::move(disk), std::move(client), std::move(prog.rest_config)
};
for (const std::string& address : prog.rest_servers)
MINFO("Listening for REST clients at " << address);
for (const std::string& address : prog.admin_rest_servers)
MINFO("Listening for REST admin clients at " << address);
// blocks until SIGINT
scanner.run(
std::move(ctx),
prog.scan_threads,
std::move(prog.lws_server_addr),
std::move(prog.lws_server_pass),
lws::scanner_options{prog.rest_config.max_subaddresses, prog.untrusted_daemon, prog.regtest}
);
}
} // 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;
}