1#include <xrpld/app/main/Application.h>
2#include <xrpld/core/Config.h>
3#include <xrpld/core/TimeKeeper.h>
4#include <xrpld/rpc/RPCCall.h>
5#include <xrpld/rpc/handlers/server_info/ServerDefinitions.h>
7#include <xrpl/basics/Log.h>
8#include <xrpl/basics/SlabAllocator.h>
9#include <xrpl/basics/base_uint.h>
10#include <xrpl/beast/core/CurrentThreadName.h>
11#include <xrpl/beast/net/IPEndpoint.h>
12#include <xrpl/beast/unit_test/suite_info.h>
13#include <xrpl/beast/utility/Journal.h>
14#include <xrpl/config/Constants.h>
15#include <xrpl/core/StartUpType.h>
16#include <xrpl/git/Git.h>
17#include <xrpl/json/json_writer.h>
18#include <xrpl/protocol/BuildInfo.h>
19#include <xrpl/protocol/SystemParameters.h>
20#include <xrpl/server/Vacuum.h>
22#include <boost/algorithm/string/classification.hpp>
23#include <boost/algorithm/string/split.hpp>
24#include <boost/algorithm/string/trim.hpp>
25#include <boost/process/v1/args.hpp>
26#include <boost/process/v1/child.hpp>
27#include <boost/process/v1/exe.hpp>
28#include <boost/program_options/options_description.hpp>
29#include <boost/program_options/positional_options.hpp>
30#include <boost/program_options/value_semantic.hpp>
31#include <boost/program_options/variables_map.hpp>
44#include <test/unit_test/multi_runner.h>
46#include <xrpl/beast/unit_test/match.h>
49#include <google/protobuf/stubs/common.h>
62#if !BOOST_OS_LINUX && !BOOST_OS_WINDOWS && !BOOST_OS_MACOS
63#error Supported platforms are: Linux, Windows and MacOS
67#if (BOOST_OS_LINUX && (BOOST_OS_WINDOWS || BOOST_OS_MACOS)) || \
68 (BOOST_OS_MACOS && (BOOST_OS_WINDOWS || BOOST_OS_LINUX)) || \
69 (BOOST_OS_WINDOWS && (BOOST_OS_LINUX || BOOST_OS_MACOS))
70#error Multiple supported platforms appear active at once
74#include <antithesis_instrumentation.h>
77namespace po = boost::program_options;
90 if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
93 if (rl.rlim_cur == RLIM_INFINITY)
99 available = rl.rlim_cur;
102 if (available < needed)
107 rl.rlim_cur = needed;
109 if (setrlimit(RLIMIT_NOFILE, &rl) == 0)
110 available = rl.rlim_cur;
114 if (needed > available)
116 j.
fatal() <<
"Insufficient number of file descriptors: " << needed
117 <<
" are needed, but only " << available <<
" are available.";
119 std::cerr <<
"Insufficient number of file descriptors: " << needed
120 <<
" are needed, but only " << available <<
" are available.\n";
135 " account_currencies <account> [<ledger>]\n"
136 " account_info <account>|<key> [<ledger>]\n"
137 " account_lines <account> <account>|\"\" [<ledger>]\n"
138 " account_channels <account> <account>|\"\" [<ledger>]\n"
139 " account_objects <account> [<ledger>]\n"
140 " account_offers <account>|<account_public_key> [<ledger>]\n"
141 " account_tx accountID [ledger_index_min [ledger_index_max "
144 " book_changes [<ledger hash|id>]\n"
145 " book_offers <taker_pays> <taker_gets> [<taker [<ledger> "
146 "[<limit> [<proof> [<marker>]]]]]\n"
147 " can_delete [<ledgerid>|<ledgerhash>|now|always|never]\n"
148 " channel_authorize <private_key> <channel_id> <drops>\n"
149 " channel_verify <public_key> <channel_id> <drops> <signature>\n"
150 " connect <ip> [<port>]\n"
152 " deposit_authorized <source_account> <destination_account> "
153 "[<ledger> [<credentials>, ...]]\n"
154 " feature [<feature> [accept|reject]]\n"
155 " fetch_info [clear]\n"
156 " gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ "
159 " json <method> <json>\n"
160 " ledger [<id>|current|closed|validated] [full]\n"
165 " ledger_request <ledger>\n"
166 " log_level [[<partition>] <severity>]\n"
168 " manifest <public_key>\n"
172 " peer_reservations_add <public_key> [<description>]\n"
173 " peer_reservations_del <public_key>\n"
174 " peer_reservations_list\n"
176 " ripple_path_find <json> [<ledger>]\n"
177 " server_definitions [<hash>]\n"
178 " server_info [counters]\n"
179 " server_state [counters]\n"
180 " sign <private_key> <tx_json> [offline]\n"
181 " sign_for <signer_address> <signer_private_key> <tx_json> "
182 "[offline] [<signature_field>]\n"
184 " simulate [<tx_blob>|<tx_json>] [<binary>]\n"
185 " submit <tx_blob>|[<private_key> <tx_json>]\n"
186 " submit_multisigned <tx_json>\n"
188 " validation_create [<seed>|<pass_phrase>|<key>]\n"
191 " validator_list_sites\n"
193 " wallet_propose [<passphrase>]\n";
208 explicit MultiSelector(
std::string const& patterns =
"")
211 boost::split(v, patterns, boost::algorithm::is_any_of(
","));
221 operator()(beast::unit_test::SuiteInfo
const& s)
223 for (
auto& sel : selectors_)
231 [[nodiscard]] std::size_t
234 return selectors_.size();
242template <
class Runner>
244anyMissing(Runner& runner, MultiSelector
const& pred)
246 if (runner.tests() == 0)
248 runner.addFailures(1);
249 std::cout <<
"Failed: No tests run" <<
std::endl;
252 if (runner.suites() < pred.size())
254 auto const missing = pred.size() - runner.suites();
255 runner.addFailures(missing);
256 std::cout <<
"Failed: " << missing <<
" filters did not match any existing test suites"
265 std::string
const& pattern,
266 std::string
const& argument,
275 using namespace beast::unit_test;
276 using namespace xrpl::test;
280 if (!child && numJobs == 1)
282 MultiRunnerParent
const parentRunner;
284 MultiRunnerChild childRunner{numJobs, quiet,
log};
285 childRunner.
arg(argument);
286 MultiSelector
const pred(pattern);
287 auto const anyFailed = childRunner.
runMulti(pred) || anyMissing(childRunner, pred);
295 MultiRunnerParent parentRunner;
296 std::vector<boost::process::v1::child> children;
298 std::string
const exeName = argv[0];
299 std::vector<std::string> args;
302 for (
int i = 1; i < argc; ++i)
308 for (std::size_t i = 0; i < numJobs; ++i)
311 boost::process::v1::exe = exeName, boost::process::v1::args = args);
314 int badChildExits = 0;
315 int terminatedChildExits = 0;
316 for (
auto& c : children)
321 if (c.exit_code() != 0)
328 ++terminatedChildExits;
333 anyMissing(parentRunner, MultiSelector(pattern));
335 if (parentRunner.
anyFailed() || (badChildExits != 0))
341 MultiRunnerChild runner{numJobs, quiet,
log};
342 runner.
arg(argument);
343 auto const anyFailed = runner.
runMulti(MultiSelector(pattern));
360 po::variables_map vm;
364 importText +=
"Import an existing node database (specified in the [";
366 importText +=
"] configuration file section) into the current ";
367 importText +=
"node database (specified in the [";
369 importText +=
"] configuration file section).";
374 po::options_description gen(
"General Options");
375 gen.add_options()(
"conf", po::value<std::string>(),
"Specify the configuration file.")(
376 "debug",
"Enable normally suppressed debug logging")(
"help,h",
"Display this message.")(
377 "newnodeid",
"Generate a new node identity for this server.")(
378 "nodeid", po::value<std::string>(),
"Specify the node identity for this server.")(
379 "quorum", po::value<std::size_t>(),
"Override the minimum validation quorum.")(
380 "silent",
"No output to the console after startup.")(
"standalone,a",
"Run with no peers.")(
381 "verbose,v",
"Verbose logging.")(
382 "definitions",
"Output server definitions as JSON and exit.")(
383 "force_ledger_present_range",
384 po::value<std::string>(),
385 "Specify the range of present ledgers for testing purposes. Min and "
386 "max values are comma separated.")(
"version",
"Display the build version.");
388 po::options_description
data(
"Ledger/Data Options");
389 data.add_options()(
"import", importText.
c_str())(
391 po::value<std::string>(),
392 "Load the specified ledger and start from the value given.")(
393 "ledgerfile", po::value<std::string>(),
"Load the specified ledger file.")(
394 "load",
"Load the current ledger from the local DB.")(
395 "net",
"Get the initial ledger from the network.")(
"replay",
"Replay a ledger close.")(
396 "trap_tx_hash", po::value<std::string>(),
"Trap a specific transaction during replay.")(
397 "start",
"Start from a fresh Ledger.")(
"vacuum",
"VACUUM the transaction db.")(
398 "valid",
"Consider the initial ledger a valid network ledger.");
400 po::options_description rpc(
"RPC Client Options");
403 "Perform rpc command - see below for available commands. "
404 "This is assumed if any positional parameters are provided.")(
406 po::value<std::string>(),
407 "Specify the IP address for RPC command. "
408 "Format: <ip-address>[':'<port-number>]")(
410 po::value<std::uint16_t>(),
411 "DEPRECATED: include with rpc_ip instead. "
412 "Specify the port number for RPC command.");
415 po::options_description
test(
"Unit Test Options");
418 "Suppress test suite messages, "
419 "including suite/case name (at start) and test log messages.")(
421 po::value<std::string>()->implicit_value(
""),
422 "Perform unit tests. The optional argument specifies one or "
423 "more comma-separated selectors. Each selector specifies a suite name, "
424 "suite name prefix, full-name (lib.module.suite), module, or library "
425 "(checked in that order).")(
427 po::value<std::string>()->implicit_value(
""),
428 "Supplies an argument string to unit tests. If provided, this argument "
429 "is made available to each suite that runs. Interpretation of the "
430 "argument is handled individually by any suite that accesses it -- "
431 "as such, it typically only make sense to provide this when running "
433 "unittest-ipv6",
"Use IPv6 localhost when running unittests (default is IPv4).")(
435 "Force unit test log message output. Only useful in combination with "
436 "--quiet, in which case log messages will print but suite/case names "
439 po::value<std::size_t>(),
440 "Number of unittest jobs to run in parallel (child processes).");
445 po::options_description hidden(
"Hidden Options");
446 hidden.add_options()(
448 po::value<vector<string>>(),
449 "Specify rpc command and parameters. This option must be repeated "
450 "for each command/param. Positional parameters also serve this "
452 "so this option is not needed for users")
454 (
"unittest-child",
"For internal use only when spawning child unit test processes.")
456 (
"unittest",
"Disabled in this build.")(
"unittest-child",
"Disabled in this build.")
458 (
"fg",
"Deprecated: server always in foreground mode.");
461 po::positional_options_description p;
462 p.add(
"parameters", -1);
464 po::options_description all;
473 po::options_description desc;
486 po::command_line_parser(argc, argv)
500 if (vm.contains(
"help"))
506 if (vm.contains(
"version"))
516 if (vm.contains(
"definitions"))
525 if (vm.count(
"unittest") || vm.count(
"unittest-child"))
535 if (vm.contains(
"unittest"))
539 if (vm.contains(
"unittest-arg"))
543 bool unittestChild =
false;
544 if (vm.contains(
"unittest-jobs"))
545 numJobs =
std::max(numJobs, vm[
"unittest-jobs"].as<std::size_t>());
546 unittestChild = vm.contains(
"unittest-child");
549 vm[
"unittest"].as<std::string>(),
551 vm.contains(
"quiet"),
552 vm.contains(
"unittest-log"),
554 vm.contains(
"unittest-ipv6"),
561 if (vm.contains(
"unittest-jobs"))
564 std::cerr <<
"xrpld: '--unittest-jobs' specified without "
566 std::cerr <<
"To run the unit tests the '--unittest' option must "
579 configFile, vm.contains(
"quiet"), vm.contains(
"silent"), vm.contains(
"standalone"));
581 if (vm.contains(
"vacuum"))
583 if (config->standalone())
585 std::cerr <<
"vacuum not applicable in standalone mode.\n";
604 if (vm.contains(
"force_ledger_present_range"))
612 vm[
"force_ledger_present_range"].as<std::string>(),
613 boost::algorithm::is_any_of(
","));
615 for (
auto& s : strVec)
630 config->forcedLedgerRangePresent.emplace(r[0], r[1]);
639 std::cerr <<
"invalid 'force_ledger_present_range' parameter. The "
640 "parameter must be two numbers separated by a comma. "
641 "The first number must be <= the second."
647 if (vm.contains(
"start"))
652 if (vm.contains(
"import"))
653 config->doImport =
true;
655 if (vm.contains(
"ledger"))
657 config->startLedger = vm[
"ledger"].as<
std::string>();
658 if (vm.contains(
"replay"))
661 if (vm.contains(
"trap_tx_hash"))
667 config->trapTxHash = tmp;
671 std::cerr <<
"Trap parameter was ill-formed, expected "
672 "valid transaction hash but received: "
683 else if (vm.contains(
"ledgerfile"))
685 config->startLedger = vm[
"ledgerfile"].as<
std::string>();
688 else if (vm.contains(
"load") || config->fastLoad)
693 if (vm.contains(
"trap_tx_hash") && !vm.contains(
"replay"))
699 if (vm.contains(
"net") && !config->fastLoad)
710 if (vm.contains(
"valid"))
712 config->startValid =
true;
717 if (vm.contains(
"rpc_ip"))
726 if (endpoint->port() == 0)
728 std::cerr <<
"No port specified in rpc_ip.\n";
729 if (vm.contains(
"rpc_port"))
731 std::cerr <<
"WARNING: using deprecated rpc_port param.\n";
734 endpoint = endpoint->atPort(vm[
"rpc_port"].as<std::uint16_t>());
735 if (endpoint->port() == 0)
750 config->rpcIp = std::move(*endpoint);
753 if (vm.contains(
"quorum"))
757 config->validationQuorum = vm[
"quorum"].as<
std::size_t>();
765 std::cerr <<
"Invalid value specified for --quorum (" << e.
what() <<
")\n";
774 if (vm.contains(
"quiet"))
778 else if (vm.contains(
"verbose"))
786 if (!vm.contains(
"parameters"))
788 if (config->hadTrailingComments())
790 JLOG(logs->journal(
"Application").warn())
791 <<
"Trailing comments were seen in your config file. "
792 <<
"The treatment of inline/trailing comments has changed "
794 <<
"Any `#` characters NOT intended to delimit comments should "
796 <<
"preceded by a \\";
804 if (vm.contains(
"debug"))
837main(
int argc,
char** argv)
857 atexit(&google::protobuf::ShutdownProtobufLibrary);
static std::optional< Endpoint > fromStringChecked(std::string const &s)
Create an Endpoint from a string.
A generic endpoint for log messages.
void arg(std::string const &s)
Set the argument string.
Outputs a Value in JSON format without formatting (not human friendly).
std::string write(Value const &root) override
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
void addFailures(std::size_t failures)
T emplace_back(T... args)
Severity
Severity level / threshold of a Journal message.
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
std::string const & getVersionString()
Server version.
int fromCommandLine(Config const &config, std::vector< std::string > const &vCmd, Logs &logs)
std::string const & getCommitHash()
std::string const & getBuildBranch()
std::atomic< bool > gEnvUseIPv4
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
int run(int argc, char **argv)
bool doVacuumDB(DatabaseCon::Setup const &setup, beast::Journal j)
doVacuumDB Creates, initialises, and performs cleanup on a database.
std::unique_ptr< beast::Journal::Sink > setDebugLogSink(std::unique_ptr< beast::Journal::Sink > sink)
Set the sink for the debug journal.
json::Value const & getServerDefinitionsJson()
bool adjustDescriptorLimit(int needed, beast::Journal j)
static std::string const & systemName()
std::unique_ptr< Application > makeApplication(std::unique_ptr< Config > config, std::unique_ptr< Logs > logs, std::unique_ptr< TimeKeeper > timeKeeper)
void printHelp(po::options_description const &desc)
DatabaseCon::Setup setupDatabaseCon(Config const &c, std::optional< beast::Journal > j=std::nullopt)
static constexpr auto kImportNodeDatabase
static constexpr auto kNodeDatabase