#include #include #include #include #include #include #include #include #include #include #include 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 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("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("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(s, "a_string"); BEAST_EXPECT(val_3 == "mystring"); auto val_4 = get(s, "not_a_key"); BEAST_EXPECT(val_4 == ""); auto val_5 = get(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(s, "positive_int"); BEAST_EXPECT(val_4 == 2); auto val_5 = get(s, "not_a_key"); BEAST_EXPECT(val_5 == 0); auto val_6 = get(s, "not_a_key", 5); BEAST_EXPECT(val_6 == 5); auto val_7 = get(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("not_a_key") == std::nullopt); try { s.get("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 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 { 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 { 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