Files
rippled/src/test/core/Config_test.cpp
Bart 3d1b3a49b3 refactor: Rename rippled.cfg to xrpld.cfg (#6098)
This change renames all occurrences of `rippled.cfg` to `xrpld.cfg`. It also provides a script to allow developers to replicate the changes in their local branch or fork to avoid conflicts. For the time being it maintains support for `rippled.cfg` as config file, if `xrpld.cfg` does not exist.
2026-01-05 14:55:12 +00:00

1659 lines
52 KiB
C++

#include <test/jtx/TestSuite.h>
#include <test/unit_test/FileDirGuard.h>
#include <xrpld/core/Config.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/server/Port.h>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <fstream>
#include <regex>
namespace xrpl {
namespace detail {
std::string
configContents(std::string const& dbPath, std::string const& validatorsFile)
{
static boost::format configContentsTemplate(R"xrpldConfig(
[server]
port_rpc
port_peer
port_wss_admin
[port_rpc]
port = 5005
ip = 127.0.0.1
admin = 127.0.0.1, ::1
protocol = https
[port_peer]
port = 51235
ip = 0.0.0.0
protocol = peer
[port_wss_admin]
port = 6006
ip = 127.0.0.1
admin = 127.0.0.1
protocol = wss
#[port_ws_public]
#port = 5005
#ip = 127.0.0.1
#protocol = wss
#-------------------------------------------------------------------------------
[node_size]
medium
# This is primary persistent datastore for xrpld. This includes transaction
# metadata, account states, and ledger headers. Helpful information can be
# found on https://xrpl.org/capacity-planning.html#node-db-type
# delete old ledgers while maintaining at least 2000. Do not require an
# external administrative command to initiate deletion.
[node_db]
type=memory
path=/Users/dummy/xrpld/config/db/rocksdb
open_files=2000
filter_bits=12
cache_mb=256
file_size_mb=8
file_size_mult=2
%1%
%2%
# This needs to be an absolute directory reference, not a relative one.
# Modify this value as required.
[debug_logfile]
/Users/dummy/xrpld/config/log/debug.log
[sntp_servers]
time.windows.com
time.apple.com
time.nist.gov
pool.ntp.org
# Where to find some other servers speaking the Ripple protocol.
#
[ips]
r.ripple.com 51235
# Turn down default logging to save disk space in the long run.
# Valid values here are trace, debug, info, warning, error, and fatal
[rpc_startup]
{ "command": "log_level", "severity": "warning" }
# Defaults to 1 ("yes") so that certificates will be validated. To allow the use
# of self-signed certificates for development or internal use, set to 0 ("no").
[ssl_verify]
0
[sqdb]
backend=sqlite
)xrpldConfig");
std::string dbPathSection =
dbPath.empty() ? "" : "[database_path]\n" + dbPath;
std::string valFileSection =
validatorsFile.empty() ? "" : "[validators_file]\n" + validatorsFile;
return boost::str(configContentsTemplate % dbPathSection % valFileSection);
}
/**
Write a xrpld config file and remove when done.
*/
class FileCfgGuard : public xrpl::detail::FileDirGuard
{
private:
path dataDir_;
bool rmDataDir_{false};
Config config_;
public:
FileCfgGuard(
beast::unit_test::suite& test,
path subDir,
path const& dbPath,
path const& configFile,
path const& validatorsFile,
bool useCounter = true,
std::string confContents = "")
: FileDirGuard(
test,
std::move(subDir),
configFile,
confContents.empty()
? configContents(dbPath.string(), validatorsFile.string())
: confContents,
useCounter)
, dataDir_(dbPath)
{
if (dbPath.empty())
dataDir_ = subdir() / path(Config::databaseDirName);
rmDataDir_ = !exists(dataDir_);
config_.setup(
file_.string(),
/* bQuiet */ true,
/* bSilent */ false,
/* bStandalone */ false);
}
Config const&
config() const
{
return config_;
}
std::string
configFile() const
{
return file().string();
}
bool
dataDirExists() const
{
return boost::filesystem::is_directory(dataDir_);
}
bool
configFileExists() const
{
return fileExists();
}
~FileCfgGuard()
{
try
{
using namespace boost::filesystem;
if (rmDataDir_)
rmDir(dataDir_);
}
catch (std::exception& e)
{
// if we throw here, just let it die.
test_.log << "Error in ~FileCfgGuard: " << e.what() << std::endl;
};
}
};
std::string
valFileContents()
{
std::string configContents(R"xrpldConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
[validator_list_sites]
recommendedxrplvalidators.com
morexrplvalidators.net
[validator_list_keys]
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
[validator_list_threshold]
2
)xrpldConfig");
return configContents;
}
/**
Write a validators.txt file and remove when done.
*/
class ValidatorsTxtGuard : public detail::FileDirGuard
{
public:
ValidatorsTxtGuard(
beast::unit_test::suite& test,
path subDir,
path const& validatorsFileName,
bool useCounter = true)
: FileDirGuard(
test,
std::move(subDir),
path(
validatorsFileName.empty() ? Config::validatorsFileName
: validatorsFileName),
valFileContents(),
useCounter)
{
}
bool
validatorsFileExists() const
{
return fileExists();
}
std::string
validatorsFile() const
{
return absolute(file()).string();
}
~ValidatorsTxtGuard()
{
}
};
} // namespace detail
class Config_test final : public TestSuite
{
private:
using path = boost::filesystem::path;
public:
void
testLegacy()
{
testcase("legacy");
Config c;
std::string toLoad(R"xrpldConfig(
[server]
port_rpc
port_peer
port_wss_admin
[ssl_verify]
0
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(c.legacy("ssl_verify") == "0");
expectException([&c] { c.legacy("server"); }); // not a single line
// set a legacy value
BEAST_EXPECT(c.legacy("not_in_file") == "");
c.legacy("not_in_file", "new_value");
BEAST_EXPECT(c.legacy("not_in_file") == "new_value");
}
void
testConfigFile()
{
testcase("config_file");
using namespace boost::filesystem;
auto const cwd = current_path();
// Test both config file names.
char const* configFiles[] = {
Config::configFileName, Config::configLegacyName};
// Config file in current directory.
for (auto const& configFile : configFiles)
{
// Use a temporary directory for testing.
beast::temp_dir td;
current_path(td.path());
path const f = td.file(configFile);
std::ofstream o(f.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the current directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
}
// Config file in HOME or XDG_CONFIG_HOME directory.
#if BOOST_OS_LINUX || BOOST_OS_MACOS
for (auto const& configFile : configFiles)
{
// Point the current working directory to a temporary directory, so
// we don't pick up an actual config file from the repository root.
beast::temp_dir td;
current_path(td.path());
// The XDG config directory is set: the config file must be in a
// subdirectory named after the system.
{
beast::temp_dir tc;
// Set the HOME and XDG_CONFIG_HOME environment variables. The
// HOME variable is not used when XDG_CONFIG_HOME is set, but
// must be set.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1);
// Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
path p = tc.file(systemName());
create_directory(p);
p = tc.file(systemName() + "/" + configFile);
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
// Restore the environment variables.
h ? setenv("HOME", h, 1) : unsetenv("HOME");
x ? setenv("XDG_CONFIG_HOME", x, 1)
: unsetenv("XDG_CONFIG_HOME");
}
// The XDG config directory is not set: the config file must be in a
// subdirectory named .config followed by the system name.
{
beast::temp_dir tc;
// Set only the HOME environment variable.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
unsetenv("XDG_CONFIG_HOME");
// Create the config file in '${HOME}/.config/[systemName]'.
std::string s = ".config";
path p = tc.file(s);
create_directory(p);
s += "/" + systemName();
p = tc.file(s);
create_directory(p);
p = tc.file(s + "/" + configFile);
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
// Restore the environment variables.
h ? setenv("HOME", h, 1) : unsetenv("HOME");
if (x)
setenv("XDG_CONFIG_HOME", x, 1);
}
}
#endif
// Restore the current working directory.
current_path(cwd);
}
void
testDbPath()
{
testcase("database_path");
using namespace boost::filesystem;
{
boost::format cc("[database_path]\n%1%\n");
auto const cwd = current_path();
path const dataDirRel("test_data_dir");
path const dataDirAbs(cwd / dataDirRel);
{
// Dummy test - do we get back what we put in
Config c;
c.loadFromString(boost::str(cc % dataDirAbs.string()));
BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string());
}
{
// Rel paths should convert to abs paths
Config c;
c.loadFromString(boost::str(cc % dataDirRel.string()));
BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string());
}
{
// No db section.
// N.B. Config::setup will give database_path a default,
// load will not.
Config c;
c.loadFromString("");
BEAST_EXPECT(c.legacy("database_path") == "");
}
}
{
// read from file absolute path
auto const cwd = current_path();
detail::DirGuard const g0(*this, "test_db");
path const dataDirRel("test_data_dir");
path const dataDirAbs(cwd / g0.subdir() / dataDirRel);
detail::FileCfgGuard const g(
*this,
g0.subdir(),
dataDirAbs,
Config::configFileName,
"",
false);
auto const& c(g.config());
BEAST_EXPECT(g.dataDirExists());
BEAST_EXPECT(g.configFileExists());
BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string());
}
{
// read from file relative path
std::string const dbPath("my_db");
detail::FileCfgGuard const g(
*this, "test_db", dbPath, Config::configFileName, "");
auto const& c(g.config());
std::string const nativeDbPath = absolute(path(dbPath)).string();
BEAST_EXPECT(g.dataDirExists());
BEAST_EXPECT(g.configFileExists());
BEAST_EXPECT(c.legacy("database_path") == nativeDbPath);
}
{
// read from file no path
detail::FileCfgGuard const g(
*this, "test_db", "", Config::configFileName, "");
auto const& c(g.config());
std::string const nativeDbPath =
absolute(g.subdir() / path(Config::databaseDirName)).string();
BEAST_EXPECT(g.dataDirExists());
BEAST_EXPECT(g.configFileExists());
BEAST_EXPECT(c.legacy("database_path") == nativeDbPath);
}
}
void
testValidatorKeys()
{
testcase("validator keys");
std::string const validationSeed = "spA4sh1qTvwq92X715tYyGQKmAKfa";
auto const token =
"eyJ2YWxpZGF0aW9uX3ByaXZhdGVfa2V5IjoiOWVkNDVmODY2MjQxY2MxOGEyNzQ3Yj"
"U0Mzg3YzA2MjU5MDc5NzJmNGU3MTkwMjMxZmFhOTM3NDU3ZmE5ZGFmNiIsIm1hbmlm"
"ZXN0IjoiSkFBQUFBRnhJZTFGdHdtaW12R3RIMmlDY01KcUM5Z1ZGS2lsR2Z3MS92Q3"
"hIWFhMcGxjMkduTWhBa0UxYWdxWHhCd0R3RGJJRDZPTVNZdU0wRkRBbHBBZ05rOFNL"
"Rm43TU8yZmRrY3dSUUloQU9uZ3U5c0FLcVhZb3VKK2wyVjBXK3NBT2tWQitaUlM2UF"
"NobEpBZlVzWGZBaUJzVkpHZXNhYWRPSmMvYUFab2tTMXZ5bUdtVnJsSFBLV1gzWXl3"
"dTZpbjhIQVNRS1B1Z0JENjdrTWFSRkd2bXBBVEhsR0tKZHZERmxXUFl5NUFxRGVkRn"
"Y1VEphMncwaTIxZXEzTVl5d0xWSlpuRk9yN0Mwa3cyQWlUelNDakl6ZGl0UTg9In0"
"=";
{
Config c;
static boost::format configTemplate(R"xrpldConfig(
[validation_seed]
%1%
[validator_token]
%2%
)xrpldConfig");
std::string error;
auto const expectedError =
"Cannot have both [validation_seed] "
"and [validator_token] config sections";
try
{
c.loadFromString(
boost::str(configTemplate % validationSeed % token));
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
}
void
testNetworkID()
{
testcase("network id");
std::string error;
Config c;
try
{
c.loadFromString(R"xrpldConfig(
[network_id]
main
)xrpldConfig");
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == "");
BEAST_EXPECT(c.NETWORK_ID == 0);
try
{
c.loadFromString(R"xrpldConfig(
)xrpldConfig");
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == "");
BEAST_EXPECT(c.NETWORK_ID == 0);
try
{
c.loadFromString(R"xrpldConfig(
[network_id]
255
)xrpldConfig");
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == "");
BEAST_EXPECT(c.NETWORK_ID == 255);
try
{
c.loadFromString(R"xrpldConfig(
[network_id]
10000
)xrpldConfig");
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == "");
BEAST_EXPECT(c.NETWORK_ID == 10000);
}
void
testValidatorsFile()
{
testcase("validators_file");
using namespace boost::filesystem;
{
// load should throw for missing specified validators file
boost::format cc("[validators_file]\n%1%\n");
std::string error;
std::string const missingPath = "/no/way/this/path/exists";
auto const expectedError =
"The file specified in [validators_file] does not exist: " +
missingPath;
try
{
Config c;
c.loadFromString(boost::str(cc % missingPath));
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load should throw for invalid [validators_file]
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
path const invalidFile = current_path() / vtg.subdir();
boost::format cc("[validators_file]\n%1%\n");
std::string error;
auto const expectedError =
"Invalid file specified in [validators_file]: " +
invalidFile.string();
try
{
Config c;
c.loadFromString(boost::str(cc % invalidFile.string()));
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load validators from config into single section
Config c;
std::string toLoad(R"xrpldConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(c.legacy("validators_file").empty());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt);
}
{
// load validator list sites and keys from config
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
[validator_list_threshold]
1
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] ==
"xrplvalidators.com");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] ==
"trustthesevalidators.gov");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] ==
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
"E566");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "1");
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::size_t(1));
}
{
// load validator list sites and keys from config
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
[validator_list_threshold]
0
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] ==
"xrplvalidators.com");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] ==
"trustthesevalidators.gov");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] ==
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
"E566");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "0");
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt);
}
{
// load should throw if [validator_list_threshold] is greater than
// the number of [validator_list_keys]
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
[validator_list_threshold]
2
)xrpldConfig");
std::string error;
auto const expectedError =
"Value in config section [validator_list_threshold] exceeds "
"the number of configured list keys";
try
{
c.loadFromString(toLoad);
fail();
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load should throw if [validator_list_threshold] is malformed
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
[validator_list_threshold]
value = 2
)xrpldConfig");
std::string error;
auto const expectedError =
"Config section [validator_list_threshold] should contain "
"single value only";
try
{
c.loadFromString(toLoad);
fail();
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load should throw if [validator_list_threshold] is negative
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
[validator_list_threshold]
-1
)xrpldConfig");
bool error = false;
try
{
c.loadFromString(toLoad);
fail();
}
catch (std::bad_cast& e)
{
error = true;
}
BEAST_EXPECT(error);
}
{
// load should throw if [validator_list_sites] is configured but
// [validator_list_keys] is not
Config c;
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
)xrpldConfig");
std::string error;
auto const expectedError =
"[validator_list_keys] config section is missing";
try
{
c.loadFromString(toLoad);
fail();
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load from specified [validators_file] absolute path
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
Config c;
boost::format cc("[validators_file]\n%1%\n");
c.loadFromString(boost::str(cc % vtg.validatorsFile()));
BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load from specified [validators_file] file name
// in config directory
std::string const valFileName = "validators.txt";
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", valFileName);
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
valFileName,
false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
BEAST_EXPECT(c.legacy("validators_file") == valFileName);
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load from specified [validators_file] relative path
// to config directory
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.txt");
auto const valFilePath = ".." / vtg.subdir() / "validators.txt";
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
valFilePath,
false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
BEAST_EXPECT(c.legacy("validators_file") == valFilePath);
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load from validators file in default location
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.txt");
detail::FileCfgGuard const rcg(
*this, vtg.subdir(), "", Config::configFileName, "", false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
BEAST_EXPECT(c.legacy("validators_file").empty());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load from specified [validators_file] instead
// of default location
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
detail::ValidatorsTxtGuard const vtgDefault(
*this, vtg.subdir(), "validators.txt", false);
BEAST_EXPECT(vtgDefault.validatorsFileExists());
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
vtg.validatorsFile(),
false);
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load validators from both config and validators file
boost::format cc(R"xrpldConfig(
[validators_file]
%1%
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
[validator_keys]
nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
[validator_list_sites]
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
)xrpldConfig");
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
Config c;
c.loadFromString(boost::str(cc % vtg.validatorsFile()));
BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 15);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 4);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 3);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
1);
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
}
{
// load should throw if [validator_list_threshold] is present both
// in xrpld.cfg and validators file
boost::format cc(R"xrpldConfig(
[validators_file]
%1%
[validator_list_threshold]
1
)xrpldConfig");
std::string error;
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
auto const expectedError =
"Config section [validator_list_threshold] should contain "
"single value only";
try
{
Config c;
c.loadFromString(boost::str(cc % vtg.validatorsFile()));
fail();
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load should throw if [validators], [validator_keys] and
// [validator_list_keys] are missing from xrpld.cfg and
// validators file
Config c;
boost::format cc("[validators_file]\n%1%\n");
std::string error;
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
auto const expectedError =
"The file specified in [validators_file] does not contain a "
"[validators], [validator_keys] or [validator_list_keys] "
"section: " +
vtg.validatorsFile();
std::ofstream o(vtg.validatorsFile());
try
{
Config c2;
c2.loadFromString(boost::str(cc % vtg.validatorsFile()));
}
catch (std::runtime_error& e)
{
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
}
void
testSetup(bool explicitPath)
{
detail::FileCfgGuard const cfg(
*this,
"testSetup",
explicitPath ? "test_db" : "",
Config::configFileName,
"");
/* FileCfgGuard has a Config object that gets loaded on
construction, but Config::setup is not reentrant, so we
need a fresh config for every test case, so ignore it.
*/
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ false,
/* bStandalone */ false);
BEAST_EXPECT(!config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(!config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 256);
BEAST_EXPECT(!config.legacy("database_path").empty());
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ false,
/* bStandalone */ false);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(!config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 256);
BEAST_EXPECT(!config.legacy("database_path").empty());
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ true,
/* bStandalone */ false);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(!config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 256);
BEAST_EXPECT(!config.legacy("database_path").empty());
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ true,
/* bStandalone */ false);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(!config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 256);
BEAST_EXPECT(!config.legacy("database_path").empty());
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ false,
/* bStandalone */ true);
BEAST_EXPECT(!config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 0);
BEAST_EXPECT(
config.legacy("database_path").empty() == !explicitPath);
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ false,
/* bStandalone */ true);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 0);
BEAST_EXPECT(
config.legacy("database_path").empty() == !explicitPath);
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ true,
/* bStandalone */ true);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 0);
BEAST_EXPECT(
config.legacy("database_path").empty() == !explicitPath);
}
{
Config config;
config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ true,
/* bStandalone */ true);
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(config.standalone());
BEAST_EXPECT(config.LEDGER_HISTORY == 0);
BEAST_EXPECT(
config.legacy("database_path").empty() == !explicitPath);
}
}
void
testPort()
{
detail::FileCfgGuard const cfg(
*this, "testPort", "", Config::configFileName, "");
auto const& conf = cfg.config();
if (!BEAST_EXPECT(conf.exists("port_rpc")))
return;
if (!BEAST_EXPECT(conf.exists("port_wss_admin")))
return;
ParsedPort rpc;
if (!unexcept([&]() { parse_Port(rpc, conf["port_rpc"], log); }))
return;
BEAST_EXPECT(rpc.admin_nets_v4.size() + rpc.admin_nets_v6.size() == 2);
ParsedPort wss;
if (!unexcept([&]() { parse_Port(wss, conf["port_wss_admin"], log); }))
return;
BEAST_EXPECT(wss.admin_nets_v4.size() + wss.admin_nets_v6.size() == 1);
}
void
testZeroPort()
{
auto const contents = std::regex_replace(
detail::configContents("", ""),
std::regex("port\\s*=\\s*\\d+"),
"port = 0");
try
{
detail::FileCfgGuard const cfg(
*this,
"testPort",
"",
Config::configFileName,
"",
true,
contents);
BEAST_EXPECT(false);
}
catch (std::exception const& ex)
{
BEAST_EXPECT(std::string_view(ex.what()).starts_with(
"Invalid value '0' for key 'port'"));
}
}
void
testWhitespace()
{
Config cfg;
/* NOTE: this string includes some explicit
* space chars in order to verify proper trimming */
std::string toLoad(R"(
[port_rpc])"
"\x20"
R"(
# comment
# indented comment
)"
"\x20\x20"
R"(
[ips])"
"\x20"
R"(
r.ripple.com 51235
[ips_fixed])"
"\x20\x20"
R"(
# COMMENT
s1.ripple.com 51235
s2.ripple.com 51235
)");
cfg.loadFromString(toLoad);
BEAST_EXPECT(
cfg.exists("port_rpc") && cfg.section("port_rpc").lines().empty() &&
cfg.section("port_rpc").values().empty());
BEAST_EXPECT(
cfg.exists(SECTION_IPS) &&
cfg.section(SECTION_IPS).lines().size() == 1 &&
cfg.section(SECTION_IPS).values().size() == 1);
BEAST_EXPECT(
cfg.exists(SECTION_IPS_FIXED) &&
cfg.section(SECTION_IPS_FIXED).lines().size() == 2 &&
cfg.section(SECTION_IPS_FIXED).values().size() == 2);
}
void
testColons()
{
Config cfg;
/* NOTE: this string includes some explicit
* space chars in order to verify proper trimming */
std::string toLoad(R"(
[port_rpc])"
"\x20"
R"(
# comment
# indented comment
)"
"\x20\x20"
R"(
[ips])"
"\x20"
R"(
r.ripple.com:51235
[ips_fixed])"
"\x20\x20"
R"(
# COMMENT
s1.ripple.com:51235
s2.ripple.com 51235
anotherserversansport
anotherserverwithport:12
1.1.1.1:1
1.1.1.1 1
12.34.12.123:12345
12.34.12.123 12345
::
2001:db8::
::1
::1:12345
[::1]:12345
2001:db8:3333:4444:5555:6666:7777:8888:12345
[2001:db8:3333:4444:5555:6666:7777:8888]:1
)");
cfg.loadFromString(toLoad);
BEAST_EXPECT(
cfg.exists("port_rpc") && cfg.section("port_rpc").lines().empty() &&
cfg.section("port_rpc").values().empty());
BEAST_EXPECT(
cfg.exists(SECTION_IPS) &&
cfg.section(SECTION_IPS).lines().size() == 1 &&
cfg.section(SECTION_IPS).values().size() == 1);
BEAST_EXPECT(
cfg.exists(SECTION_IPS_FIXED) &&
cfg.section(SECTION_IPS_FIXED).lines().size() == 15 &&
cfg.section(SECTION_IPS_FIXED).values().size() == 15);
BEAST_EXPECT(cfg.IPS[0] == "r.ripple.com 51235");
BEAST_EXPECT(cfg.IPS_FIXED[0] == "s1.ripple.com 51235");
BEAST_EXPECT(cfg.IPS_FIXED[1] == "s2.ripple.com 51235");
BEAST_EXPECT(cfg.IPS_FIXED[2] == "anotherserversansport");
BEAST_EXPECT(cfg.IPS_FIXED[3] == "anotherserverwithport 12");
BEAST_EXPECT(cfg.IPS_FIXED[4] == "1.1.1.1 1");
BEAST_EXPECT(cfg.IPS_FIXED[5] == "1.1.1.1 1");
BEAST_EXPECT(cfg.IPS_FIXED[6] == "12.34.12.123 12345");
BEAST_EXPECT(cfg.IPS_FIXED[7] == "12.34.12.123 12345");
// all ipv6 should be ignored by colon replacer, howsoever formatted
BEAST_EXPECT(cfg.IPS_FIXED[8] == "::");
BEAST_EXPECT(cfg.IPS_FIXED[9] == "2001:db8::");
BEAST_EXPECT(cfg.IPS_FIXED[10] == "::1");
BEAST_EXPECT(cfg.IPS_FIXED[11] == "::1:12345");
BEAST_EXPECT(cfg.IPS_FIXED[12] == "[::1]:12345");
BEAST_EXPECT(
cfg.IPS_FIXED[13] ==
"2001:db8:3333:4444:5555:6666:7777:8888:12345");
BEAST_EXPECT(
cfg.IPS_FIXED[14] == "[2001:db8:3333:4444:5555:6666:7777:8888]:1");
}
void
testComments()
{
struct TestCommentData
{
std::string_view line;
std::string_view field;
std::string_view expect;
bool had_comment;
};
std::array<TestCommentData, 13> tests = {
{{"password = aaaa\\#bbbb", "password", "aaaa#bbbb", false},
{"password = aaaa#bbbb", "password", "aaaa", true},
{"password = aaaa #bbbb", "password", "aaaa", true},
// since the value is all comment, this doesn't parse as k=v :
{"password = #aaaa #bbbb", "", "password =", true},
{"password = aaaa\\# #bbbb", "password", "aaaa#", true},
{"password = aaaa\\##bbbb", "password", "aaaa#", true},
{"aaaa#bbbb", "", "aaaa", true},
{"aaaa\\#bbbb", "", "aaaa#bbbb", false},
{"aaaa\\##bbbb", "", "aaaa#", true},
{"aaaa #bbbb", "", "aaaa", true},
{"1 #comment", "", "1", true},
{"#whole thing is comment", "", "", false},
{" #whole comment with space", "", "", false}}};
for (auto const& t : tests)
{
Section s;
s.append(t.line.data());
BEAST_EXPECT(s.had_trailing_comments() == t.had_comment);
if (t.field.empty())
{
BEAST_EXPECTS(s.legacy() == t.expect, s.legacy());
}
else
{
std::string field;
BEAST_EXPECTS(set(field, t.field.data(), s), t.line);
BEAST_EXPECTS(field == t.expect, t.line);
}
}
{
Section s;
s.append("online_delete = 3000");
std::uint32_t od = 0;
BEAST_EXPECT(set(od, "online_delete", s));
BEAST_EXPECTS(od == 3000, *(s.get<std::string>("online_delete")));
}
{
Section s;
s.append("online_delete = 2000 #my comment on this");
std::uint32_t od = 0;
BEAST_EXPECT(set(od, "online_delete", s));
BEAST_EXPECTS(od == 2000, *(s.get<std::string>("online_delete")));
}
}
void
testGetters()
{
using namespace std::string_literals;
Section s{"MySection"};
s.append("a_string = mystring");
s.append("positive_int = 2");
s.append("negative_int = -3");
s.append("bool_ish = 1");
{
auto val_1 = "value 1"s;
BEAST_EXPECT(set(val_1, "a_string", s));
BEAST_EXPECT(val_1 == "mystring");
auto val_2 = "value 2"s;
BEAST_EXPECT(!set(val_2, "not_a_key", s));
BEAST_EXPECT(val_2 == "value 2");
BEAST_EXPECT(!set(val_2, "default"s, "not_a_key", s));
BEAST_EXPECT(val_2 == "default");
auto val_3 = get<std::string>(s, "a_string");
BEAST_EXPECT(val_3 == "mystring");
auto val_4 = get<std::string>(s, "not_a_key");
BEAST_EXPECT(val_4 == "");
auto val_5 = get<std::string>(s, "not_a_key", "default");
BEAST_EXPECT(val_5 == "default");
auto val_6 = "value 6"s;
BEAST_EXPECT(get_if_exists(s, "a_string", val_6));
BEAST_EXPECT(val_6 == "mystring");
auto val_7 = "value 7"s;
BEAST_EXPECT(!get_if_exists(s, "not_a_key", val_7));
BEAST_EXPECT(val_7 == "value 7");
}
{
int val_1 = 1;
BEAST_EXPECT(set(val_1, "positive_int", s));
BEAST_EXPECT(val_1 == 2);
int val_2 = 2;
BEAST_EXPECT(set(val_2, "negative_int", s));
BEAST_EXPECT(val_2 == -3);
int val_3 = 3;
BEAST_EXPECT(!set(val_3, "a_string", s));
BEAST_EXPECT(val_3 == 3);
auto val_4 = get<int>(s, "positive_int");
BEAST_EXPECT(val_4 == 2);
auto val_5 = get<int>(s, "not_a_key");
BEAST_EXPECT(val_5 == 0);
auto val_6 = get<int>(s, "not_a_key", 5);
BEAST_EXPECT(val_6 == 5);
auto val_7 = get<int>(s, "a_string", 6);
BEAST_EXPECT(val_7 == 6);
int val_8 = 8;
BEAST_EXPECT(get_if_exists(s, "positive_int", val_8));
BEAST_EXPECT(val_8 == 2);
auto val_9 = 9;
BEAST_EXPECT(!get_if_exists(s, "not_a_key", val_9));
BEAST_EXPECT(val_9 == 9);
auto val_10 = 10;
BEAST_EXPECT(!get_if_exists(s, "a_string", val_10));
BEAST_EXPECT(val_10 == 10);
BEAST_EXPECT(s.get<int>("not_a_key") == std::nullopt);
try
{
s.get<int>("a_string");
fail();
}
catch (boost::bad_lexical_cast&)
{
pass();
}
}
{
bool flag_1 = false;
BEAST_EXPECT(get_if_exists(s, "bool_ish", flag_1));
BEAST_EXPECT(flag_1 == true);
bool flag_2 = false;
BEAST_EXPECT(!get_if_exists(s, "not_a_key", flag_2));
BEAST_EXPECT(flag_2 == false);
}
}
void
testAmendment()
{
testcase("amendment");
struct ConfigUnit
{
std::string unit;
std::uint32_t numSeconds;
std::uint32_t configVal;
bool shouldPass;
};
std::vector<ConfigUnit> units = {
{"seconds", 1, 15 * 60, false},
{"minutes", 60, 14, false},
{"minutes", 60, 15, true},
{"hours", 3600, 10, true},
{"days", 86400, 10, true},
{"weeks", 604800, 2, true},
{"months", 2592000, 1, false},
{"years", 31536000, 1, false}};
std::string space = "";
for (auto& [unit, sec, val, shouldPass] : units)
{
Config c;
std::string toLoad(R"xrpldConfig(
[amendment_majority_time]
)xrpldConfig");
toLoad += std::to_string(val) + space + unit;
space = space == "" ? " " : "";
try
{
c.loadFromString(toLoad);
if (shouldPass)
BEAST_EXPECT(
c.AMENDMENT_MAJORITY_TIME.count() == val * sec);
else
fail();
}
catch (std::runtime_error&)
{
if (!shouldPass)
pass();
else
fail();
}
}
}
void
testOverlay()
{
testcase("overlay: unknown time");
auto testUnknown =
[](std::string value) -> std::optional<std::chrono::seconds> {
try
{
Config c;
c.loadFromString("[overlay]\nmax_unknown_time=" + value);
return c.MAX_UNKNOWN_TIME;
}
catch (std::runtime_error&)
{
return {};
}
};
// Failures
BEAST_EXPECT(!testUnknown("none"));
BEAST_EXPECT(!testUnknown("0.5"));
BEAST_EXPECT(!testUnknown("180 seconds"));
BEAST_EXPECT(!testUnknown("9 minutes"));
// Below lower bound
BEAST_EXPECT(!testUnknown("299"));
// In bounds
BEAST_EXPECT(testUnknown("300") == std::chrono::seconds{300});
BEAST_EXPECT(testUnknown("301") == std::chrono::seconds{301});
BEAST_EXPECT(testUnknown("1799") == std::chrono::seconds{1799});
BEAST_EXPECT(testUnknown("1800") == std::chrono::seconds{1800});
// Above upper bound
BEAST_EXPECT(!testUnknown("1801"));
testcase("overlay: diverged time");
// In bounds:
auto testDiverged =
[](std::string value) -> std::optional<std::chrono::seconds> {
try
{
Config c;
c.loadFromString("[overlay]\nmax_diverged_time=" + value);
return c.MAX_DIVERGED_TIME;
}
catch (std::runtime_error&)
{
return {};
}
};
// Failures
BEAST_EXPECT(!testDiverged("none"));
BEAST_EXPECT(!testDiverged("0.5"));
BEAST_EXPECT(!testDiverged("180 seconds"));
BEAST_EXPECT(!testDiverged("9 minutes"));
// Below lower bound
BEAST_EXPECT(!testDiverged("0"));
BEAST_EXPECT(!testDiverged("59"));
// In bounds
BEAST_EXPECT(testDiverged("60") == std::chrono::seconds{60});
BEAST_EXPECT(testDiverged("61") == std::chrono::seconds{61});
BEAST_EXPECT(testDiverged("899") == std::chrono::seconds{899});
BEAST_EXPECT(testDiverged("900") == std::chrono::seconds{900});
// Above upper bound
BEAST_EXPECT(!testDiverged("901"));
}
void
run() override
{
testLegacy();
testConfigFile();
testDbPath();
testValidatorKeys();
testValidatorsFile();
testSetup(false);
testSetup(true);
testPort();
testZeroPort();
testWhitespace();
testColons();
testComments();
testGetters();
testAmendment();
testOverlay();
testNetworkID();
}
};
BEAST_DEFINE_TESTSUITE(Config, core, xrpl);
} // namespace xrpl