xrpld
Loading...
Searching...
No Matches
Config_test.cpp
1#include <test/jtx/TestSuite.h>
2#include <test/unit_test/FileDirGuard.h>
3
4#include <xrpld/core/Config.h>
5
6#include <xrpl/basics/FileUtilities.h>
7#include <xrpl/beast/unit_test/suite.h>
8#include <xrpl/config/BasicConfig.h>
9#include <xrpl/config/Constants.h>
10#include <xrpl/protocol/SystemParameters.h> // IWYU pragma: keep
11#include <xrpl/server/Port.h>
12
13#include <boost/lexical_cast/bad_lexical_cast.hpp>
14
15#include <array>
16#include <cstddef>
17#include <cstdint>
18#include <cstdlib>
19#include <exception>
20#include <filesystem>
21#include <format>
22#include <fstream>
23#include <optional>
24#include <ostream>
25#include <regex>
26#include <stdexcept>
27#include <string>
28#include <string_view>
29#include <typeinfo>
30#include <utility>
31#include <vector>
32
33namespace xrpl {
34namespace detail {
35std::string
36configContents(std::string const& dbPath, std::string const& validatorsFile)
37{
38 static constexpr char const* kConfigContentsTemplate = R"xrpldConfig(
39[server]
40port_rpc
41port_peer
42port_wss_admin
43
44[port_rpc]
45port = 5005
46ip = 127.0.0.1
47admin = 127.0.0.1, ::1
48protocol = https
49
50[port_peer]
51port = 51235
52ip = 0.0.0.0
53protocol = peer
54
55[port_wss_admin]
56port = 6006
57ip = 127.0.0.1
58admin = 127.0.0.1
59protocol = wss
60
61#[port_ws_public]
62#port = 5005
63#ip = 127.0.0.1
64#protocol = wss
65
66#-------------------------------------------------------------------------------
67
68[node_size]
69medium
70
71# This is primary persistent datastore for xrpld. This includes transaction
72# metadata, account states, and ledger headers. Helpful information can be
73# found on https://xrpl.org/capacity-planning.html#node-db-type
74# delete old ledgers while maintaining at least 2000. Do not require an
75# external administrative command to initiate deletion.
76[node_db]
77type=memory
78path=/Users/dummy/xrpld/config/db/rocksdb
79open_files=2000
80filter_bits=12
81cache_mb=256
82file_size_mb=8
83file_size_mult=2
84
85{}
86
87{}
88
89# This needs to be an absolute directory reference, not a relative one.
90# Modify this value as required.
91[debug_logfile]
92/Users/dummy/xrpld/config/log/debug.log
93
94[sntp_servers]
95time.windows.com
96time.apple.com
97time.nist.gov
98pool.ntp.org
99
100# Where to find some other servers speaking the XRPL protocol.
101#
102[ips]
103r.ripple.com 51235
104
105# Turn down default logging to save disk space in the long run.
106# Valid values here are trace, debug, info, warning, error, and fatal
107[rpc_startup]
108{{ "command": "log_level", "severity": "warning" }}
109
110# Defaults to 1 ("yes") so that certificates will be validated. To allow the use
111# of self-signed certificates for development or internal use, set to 0 ("no").
112[ssl_verify]
1130
114
115[sqdb]
116backend=sqlite
117)xrpldConfig";
118
119 std::string dbPathSection = dbPath.empty() ? "" : "[database_path]\n" + dbPath;
120 std::string valFileSection =
121 validatorsFile.empty() ? "" : "[validators_file]\n" + validatorsFile;
122 return std::format(kConfigContentsTemplate, dbPathSection, valFileSection);
123}
124
125// setenv and unsetenv are POSIX only, and MSVC does not have them.
126#ifndef _MSC_VER
127
136 */
137[[nodiscard]] std::optional<std::string>
138envVar(char const* name)
139{
140 if (char const* const value = std::getenv(name); value != nullptr)
141 return std::string{value};
142
143 return std::nullopt;
144}
145
149class EnvVarGuard
150{
151private:
152 std::string const name_;
153 std::optional<std::string> const saved_;
154
161 static void
162 apply(char const* name, std::optional<std::string> const& value) noexcept
163 {
164 if (value)
166 setenv(name, value->c_str(), 1);
167 }
168 else
169 {
170 unsetenv(name);
172 }
173
174public:
175
178 * @param name Name of the environment variable to set.
179 * @param value Value to set, or std::nullopt to remove the variable.
180 */
181 [[nodiscard]] EnvVarGuard(char const* name, std::optional<std::string> const& value)
182 : name_(name), saved_(envVar(name))
184 apply(name_.c_str(), value);
185 }
186
187 EnvVarGuard(EnvVarGuard const&) = delete;
188 EnvVarGuard&
189 operator=(EnvVarGuard const&) = delete;
190
194 ~EnvVarGuard()
195 {
196 apply(name_.c_str(), saved_);
197 }
198};
199
200#endif // _MSC_VER
201
202
203
206{
207private:
209
210 bool rmDataDir_{false};
211
213
214public:
217 Path subDir,
218 Path const& dbPath,
219 Path const& configFile,
220 Path const& validatorsFile,
221 bool useCounter = true,
222 std::string confContents = "")
223 : FileDirGuard(
224 test,
225 std::move(subDir),
227 confContents.empty() ? configContents(dbPath.string(), validatorsFile.string())
228 : confContents,
229 useCounter)
230 , dataDir_(dbPath)
231 {
232 if (dbPath.empty())
234
235 rmDataDir_ = !exists(dataDir_);
237 file_.string(),
238 /* bQuiet */ true,
239 /* bSilent */ false,
240 /* bStandalone */ false);
241 }
242
243 [[nodiscard]] Config const&
244 config() const
245 {
246 return config_;
247 }
248
249 [[nodiscard]] std::string
250 configFile() const
251 {
252 return file().string();
253 }
254
255 [[nodiscard]] bool
256 dataDirExists() const
257 {
259 }
260
261 [[nodiscard]] bool
262 configFileExists() const
263 {
264 return fileExists();
265 }
266
268 {
269 try
270 {
271 using namespace std::filesystem;
272 if (rmDataDir_)
274 }
275 catch (std::exception& e)
276 {
277 // if we throw here, just let it die.
278 test_.log << "Error in ~FileCfgGuard: " << e.what() << std::endl;
279 };
280 }
281};
282
283std::string
285{
286 std::string configContents(R"xrpldConfig(
287[validators]
288n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
289n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
290n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
291n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
292n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
293
294[validator_keys]
295nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
296nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
297nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
298
299[validator_list_sites]
300recommended-xrpl-validators.com
301more-xrpl-validators.net
302
303[validator_list_keys]
30403E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
305030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
306
307[validator_list_threshold]
3082
309)xrpldConfig");
310 return configContents;
311}
312
316class ValidatorsTxtGuard : public detail::FileDirGuard
317{
318public:
320 beast::unit_test::Suite& test,
321 Path subDir,
322 Path const& validatorsFileName,
323 bool useCounter = true)
324 : FileDirGuard(
325 test,
326 std::move(subDir),
327 Path(validatorsFileName.empty() ? Config::kValidatorsFileName : validatorsFileName),
329 useCounter)
330 {
331 }
332
333 [[nodiscard]] bool
335 {
336 return fileExists();
337 }
338
339 [[nodiscard]] std::string
340 validatorsFile() const
341 {
342 return absolute(file()).string();
343 }
344
345 ~ValidatorsTxtGuard() = default;
346};
347} // namespace detail
348
349class Config_test final : public TestSuite
350{
351private:
353
354public:
355 void
357 {
358 testcase("legacy");
359
360 Config c;
361
362 std::string const toLoad(R"xrpldConfig(
363[server]
364port_rpc
365port_peer
366port_wss_admin
367
368[ssl_verify]
3690
370)xrpldConfig");
371
372 c.loadFromString(toLoad);
373
374 BEAST_EXPECT(c.legacy(Sections::kSslVerify) == "0");
376 [&c] { [[maybe_unused]] auto _ = c.legacy(Sections::kServer); }); // not a single line
377
378 // set a legacy value
379 BEAST_EXPECT(c.legacy("not_in_file").empty());
380 c.legacy("not_in_file", "new_value");
381 BEAST_EXPECT(c.legacy("not_in_file") == "new_value");
382 }
383 void
385 {
386 testcase("config_file");
387
388 using namespace std::filesystem;
389 auto const cwd = current_path();
390
391 // Test both config file names.
393
394 // Config file in current directory.
395 for (auto const& configFile : configFiles)
396 {
397 // Use a temporary directory for testing.
398 TempDir const td;
399 current_path(td.path());
400 Path const f = td.file(std::string{configFile});
401 std::ofstream o(f.string());
402 o << detail::configContents("", "");
403 o.close();
404
405 // Load the config file from the current directory and verify it.
406 Config c;
407 c.setup("", true, false, true);
408 BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
409 BEAST_EXPECT(
410 c.section(Sections::kDebugLogfile).values()[0] ==
411 "/Users/dummy/xrpld/config/log/debug.log");
412 }
413
414 // Config file in HOME or XDG_CONFIG_HOME directory.
415#ifndef _MSC_VER
416 // Save the values the guards below must put back.
417 auto const home = detail::envVar("HOME");
418 auto const xdgConfigHome = detail::envVar("XDG_CONFIG_HOME");
419
420 for (auto const& configFile : configFiles)
421 {
422 // Point the current working directory to a temporary directory, so
423 // we don't pick up an actual config file from the repository root.
424 TempDir const td;
425 current_path(td.path());
426
427 // The XDG config directory is set: the config file must be in a
428 // subdirectory named after the system.
429 {
430 TempDir const tc;
431
432 // The HOME variable is not used when XDG_CONFIG_HOME is set,
433 // but must be set. Both guards are declared after tc, so they
434 // restore before it is removed.
435 detail::EnvVarGuard const homeGuard("HOME", tc.path());
436 detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", tc.path());
437
438 // Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
439 Path p = tc.file(systemName());
441 p = tc.file(systemName() + "/" + std::string{configFile});
442 std::ofstream o(p.string());
443 o << detail::configContents("", "");
444 o.close();
445
446 // Load the config file from the config directory and verify it.
447 Config c;
448 c.setup("", true, false, true);
449 BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
450 BEAST_EXPECT(
451 c.section(Sections::kDebugLogfile).values()[0] ==
452 "/Users/dummy/xrpld/config/log/debug.log");
453 }
454
455 // Both guards are gone: both variables must hold their old values.
456 BEAST_EXPECT(detail::envVar("HOME") == home);
457 BEAST_EXPECT(detail::envVar("XDG_CONFIG_HOME") == xdgConfigHome);
458
459 // The XDG config directory is not set: the config file must be in a
460 // subdirectory named .config followed by the system name.
461 {
462 TempDir const tc;
463
464 // Both guards are declared after tc, so they restore before it
465 // is removed.
466 detail::EnvVarGuard const homeGuard("HOME", tc.path());
467 detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt);
468
469 // Create the config file in '${HOME}/.config/[systemName]'.
470 std::string s = ".config";
471 Path p = tc.file(s);
473 s += "/" + systemName();
474 p = tc.file(s);
476 p = tc.file(s + "/" + std::string{configFile});
477 std::ofstream o(p.string());
478 o << detail::configContents("", "");
479 o.close();
480
481 // Load the config file from the config directory and verify it.
482 Config c;
483 c.setup("", true, false, true);
484 BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
485 BEAST_EXPECT(
486 c.section(Sections::kDebugLogfile).values()[0] ==
487 "/Users/dummy/xrpld/config/log/debug.log");
488 }
489
490 // Both guards are gone: both variables must hold their old values.
491 BEAST_EXPECT(detail::envVar("HOME") == home);
492 BEAST_EXPECT(detail::envVar("XDG_CONFIG_HOME") == xdgConfigHome);
493 }
494#endif // _MSC_VER
495
496 // Restore the current working directory.
497 current_path(cwd);
498 }
499 void
500 testDbPath()
501 {
502 testcase("database_path");
503
504 using namespace std::filesystem;
505 {
506 constexpr char const* cc = "[database_path]\n{}\n";
507
508 auto const cwd = current_path();
509 Path const dataDirRel("test_data_dir");
510 Path const dataDirAbs(cwd / dataDirRel);
511 {
512 // Dummy test - do we get back what we put in
513 Config c;
514 c.loadFromString(std::format(cc, dataDirAbs.string()));
515 BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string());
516 }
517 {
518 // Rel paths should convert to abs paths
519 Config c;
520 c.loadFromString(std::format(cc, dataDirRel.string()));
521 BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string());
522 }
523 {
524 // No db section.
525 // N.B. Config::setup will give database_path a default,
526 // load will not.
527 Config c;
528 c.loadFromString("");
529 BEAST_EXPECT(c.legacy(Sections::kDatabasePath).empty());
530 }
531 }
532 {
533 // read from file absolute path
534 auto const cwd = current_path();
535 detail::DirGuard const g0(*this, "test_db");
536 Path const dataDirRel("test_data_dir");
537 Path const dataDirAbs(cwd / g0.subdir() / dataDirRel);
538 detail::FileCfgGuard const g(
539 *this, g0.subdir(), dataDirAbs, Config::kConfigFileName, "", false);
540 auto const& c(g.config());
541 BEAST_EXPECT(g.dataDirExists());
542 BEAST_EXPECT(g.configFileExists());
543 BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string());
544 }
545 {
546 // read from file relative path
547 std::string const dbPath("my_db");
548 detail::FileCfgGuard const g(*this, "test_db", dbPath, Config::kConfigFileName, "");
549 auto const& c(g.config());
550 std::string const nativeDbPath = absolute(Path(dbPath)).string();
551 BEAST_EXPECT(g.dataDirExists());
552 BEAST_EXPECT(g.configFileExists());
553 BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == nativeDbPath);
554 }
555 {
556 // read from file no path
557 detail::FileCfgGuard const g(*this, "test_db", "", Config::kConfigFileName, "");
558 auto const& c(g.config());
559 std::string const nativeDbPath =
560 absolute(g.subdir() / Path(Config::kDatabaseDirName)).string();
561 BEAST_EXPECT(g.dataDirExists());
562 BEAST_EXPECT(g.configFileExists());
563 BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == nativeDbPath);
564 }
565 }
566
567 void
569 {
570 testcase("validator keys");
571
572 std::string const validationSeed = "spA4sh1qTvwq92X715tYyGQKmAKfa";
573
574 auto const token =
575 "eyJ2YWxpZGF0aW9uX3ByaXZhdGVfa2V5IjoiOWVkNDVmODY2MjQxY2MxOGEyNzQ3Yj"
576 "U0Mzg3YzA2MjU5MDc5NzJmNGU3MTkwMjMxZmFhOTM3NDU3ZmE5ZGFmNiIsIm1hbmlm"
577 "ZXN0IjoiSkFBQUFBRnhJZTFGdHdtaW12R3RIMmlDY01KcUM5Z1ZGS2lsR2Z3MS92Q3"
578 "hIWFhMcGxjMkduTWhBa0UxYWdxWHhCd0R3RGJJRDZPTVNZdU0wRkRBbHBBZ05rOFNL"
579 "Rm43TU8yZmRrY3dSUUloQU9uZ3U5c0FLcVhZb3VKK2wyVjBXK3NBT2tWQitaUlM2UF"
580 "NobEpBZlVzWGZBaUJzVkpHZXNhYWRPSmMvYUFab2tTMXZ5bUdtVnJsSFBLV1gzWXl3"
581 "dTZpbjhIQVNRS1B1Z0JENjdrTWFSRkd2bXBBVEhsR0tKZHZERmxXUFl5NUFxRGVkRn"
582 "Y1VEphMncwaTIxZXEzTVl5d0xWSlpuRk9yN0Mwa3cyQWlUelNDakl6ZGl0UTg9In0"
583 "=";
584
585 {
586 Config c;
587 static constexpr char const* kConfigTemplate = R"xrpldConfig(
588[validation_seed]
589{}
590
591[validator_token]
592{}
593)xrpldConfig";
594 std::string error;
595 auto const expectedError =
596 "Cannot have both [validation_seed] "
597 "and [validator_token] config sections";
598 try
599 {
600 c.loadFromString(std::format(kConfigTemplate, validationSeed, token));
601 }
602 catch (std::runtime_error const& e)
603 {
604 error = e.what();
605 }
606 BEAST_EXPECT(error == expectedError);
607 }
608 }
609
610 void
612 {
613 testcase("network id");
614 std::string error;
615 Config c;
616 try
617 {
618 c.loadFromString(R"xrpldConfig(
619[network_id]
620main
621)xrpldConfig");
622 }
623 catch (std::runtime_error const& e)
624 {
625 error = e.what();
626 }
627
628 BEAST_EXPECT(error.empty());
629 BEAST_EXPECT(c.networkId == 0);
630
631 try
632 {
633 c.loadFromString(R"xrpldConfig(
634)xrpldConfig");
635 }
636 catch (std::runtime_error const& e)
637 {
638 error = e.what();
639 }
640
641 BEAST_EXPECT(error.empty());
642 BEAST_EXPECT(c.networkId == 0);
643
644 try
645 {
646 c.loadFromString(R"xrpldConfig(
647[network_id]
648255
649)xrpldConfig");
650 }
651 catch (std::runtime_error const& e)
653 error = e.what();
654 }
655
656 BEAST_EXPECT(error.empty());
657 BEAST_EXPECT(c.networkId == 255);
658
659 try
660 {
661 c.loadFromString(R"xrpldConfig(
662[network_id]
66310000
664)xrpldConfig");
665 }
666 catch (std::runtime_error const& e)
667 {
668 error = e.what();
669 }
670
671 BEAST_EXPECT(error.empty());
672 BEAST_EXPECT(c.networkId == 10000);
673 }
674
675 void
677 {
678 testcase("validators_file");
679
680 using namespace std::filesystem;
681 {
682 // load should throw for missing specified validators file
683 constexpr char const* cc = "[validators_file]\n{}\n";
684 std::string error;
685 std::string const missingPath = "/no/way/this/path/exists";
686 auto const expectedError =
687 "The file specified in [validators_file] does not exist: " + missingPath;
688 try
689 {
690 Config c;
691 c.loadFromString(std::format(cc, missingPath));
692 }
693 catch (std::runtime_error const& e)
694 {
695 error = e.what();
696 }
697 BEAST_EXPECT(error == expectedError);
698 }
699 {
700 // load should throw for invalid [validators_file]
701 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
702 Path const invalidFile = current_path() / vtg.subdir();
703 constexpr char const* cc = "[validators_file]\n{}\n";
704 std::string error;
705 auto const expectedError =
706 "Invalid file specified in [validators_file]: " + invalidFile.string();
707 try
708 {
709 Config c;
710 c.loadFromString(std::format(cc, invalidFile.string()));
711 }
712 catch (std::runtime_error const& e)
713 {
714 error = e.what();
715 }
716 BEAST_EXPECT(error == expectedError);
717 }
718 {
719 // load validators from config into single section
720 Config c;
721 std::string const toLoad(R"xrpldConfig(
722[validators]
723n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
724n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
725n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
726
727[validator_keys]
728nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
729nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
730)xrpldConfig");
731 c.loadFromString(toLoad);
732 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile).empty());
733 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 5);
734 BEAST_EXPECT(c.validatorListThreshold == std::nullopt);
735 }
736 {
737 // load validator list sites and keys from config
738 Config c;
739 std::string const toLoad(R"xrpldConfig(
740[validator_list_sites]
741xrpl-validators.com
742trust-these-validators.gov
743
744[validator_list_keys]
745021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
746
747[validator_list_threshold]
7481
749)xrpldConfig");
750 c.loadFromString(toLoad);
751 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
752 BEAST_EXPECT(
753 c.section(Sections::kValidatorListSites).values()[0] == "xrpl-validators.com");
754 BEAST_EXPECT(
755 c.section(Sections::kValidatorListSites).values()[1] ==
756 "trust-these-validators.gov");
757 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 1);
758 BEAST_EXPECT(
759 c.section(Sections::kValidatorListKeys).values()[0] ==
760 "021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
761 "E566");
762 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
763 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values()[0] == "1");
764 BEAST_EXPECT(c.validatorListThreshold == std::size_t(1));
765 }
766 {
767 // load validator list sites and keys from config
768 Config c;
769 std::string const toLoad(R"xrpldConfig(
770[validator_list_sites]
771xrpl-validators.com
772trust-these-validators.gov
773
774[validator_list_keys]
775021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
776
777[validator_list_threshold]
7780
779)xrpldConfig");
780 c.loadFromString(toLoad);
781 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
782 BEAST_EXPECT(
783 c.section(Sections::kValidatorListSites).values()[0] == "xrpl-validators.com");
784 BEAST_EXPECT(
785 c.section(Sections::kValidatorListSites).values()[1] ==
786 "trust-these-validators.gov");
787 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 1);
788 BEAST_EXPECT(
789 c.section(Sections::kValidatorListKeys).values()[0] ==
790 "021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
791 "E566");
792 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
793 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values()[0] == "0");
794 BEAST_EXPECT(c.validatorListThreshold == std::nullopt);
795 }
796 {
797 // load should throw if [validator_list_threshold] is greater than
798 // the number of [validator_list_keys]
799 Config c;
800 std::string const toLoad(R"xrpldConfig(
801[validator_list_sites]
802xrpl-validators.com
803trust-these-validators.gov
804
805[validator_list_keys]
806021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
807
808[validator_list_threshold]
8092
810)xrpldConfig");
811 std::string error;
812 auto const expectedError =
813 "Value in config section [validator_list_threshold] exceeds "
814 "the number of configured list keys";
815 try
816 {
817 c.loadFromString(toLoad);
818 fail();
819 }
820 catch (std::runtime_error const& e)
821 {
822 error = e.what();
823 }
824 BEAST_EXPECT(error == expectedError);
825 }
826 {
827 // load should throw if [validator_list_threshold] is malformed
828 Config c;
829 std::string const toLoad(R"xrpldConfig(
830[validator_list_sites]
831xrpl-validators.com
832trust-these-validators.gov
833
834[validator_list_keys]
835021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
836
837[validator_list_threshold]
838value = 2
839)xrpldConfig");
840 std::string error;
841 auto const expectedError =
842 "Config section [validator_list_threshold] should contain "
843 "single value only";
844 try
845 {
846 c.loadFromString(toLoad);
847 fail();
848 }
849 catch (std::runtime_error const& e)
850 {
851 error = e.what();
852 }
853 BEAST_EXPECT(error == expectedError);
854 }
855 {
856 // load should throw if [validator_list_threshold] is negative
857 Config c;
858 std::string const toLoad(R"xrpldConfig(
859[validator_list_sites]
860xrpl-validators.com
861trust-these-validators.gov
862
863[validator_list_keys]
864021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
865
866[validator_list_threshold]
867-1
868)xrpldConfig");
869 bool error = false;
870 try
871 {
872 c.loadFromString(toLoad);
873 fail();
874 }
875 catch (std::bad_cast& e)
876 {
877 error = true;
878 }
879 BEAST_EXPECT(error);
880 }
881 {
882 // load should throw if [validator_list_sites] is configured but
883 // [validator_list_keys] is not
884 Config c;
885 std::string const toLoad(R"xrpldConfig(
886[validator_list_sites]
887xrpl-validators.com
888trust-these-validators.gov
889)xrpldConfig");
890 std::string error;
891 auto const expectedError = "[validator_list_keys] config section is missing";
892 try
893 {
894 c.loadFromString(toLoad);
895 fail();
896 }
897 catch (std::runtime_error const& e)
898 {
899 error = e.what();
900 }
901 BEAST_EXPECT(error == expectedError);
902 }
903 {
904 // load from specified [validators_file] absolute path
905 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
906 BEAST_EXPECT(vtg.validatorsFileExists());
907 Config c;
908 constexpr char const* cc = "[validators_file]\n{}\n";
909 c.loadFromString(std::format(cc, vtg.validatorsFile()));
910 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile());
911 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8);
912 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
913 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2);
914 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
915 BEAST_EXPECT(c.validatorListThreshold == 2);
916 }
917 {
918 // load from specified [validators_file] file name
919 // in config directory
920 std::string const valFileName = "validators.txt";
921 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", valFileName);
922 detail::FileCfgGuard const rcg(
923 *this, vtg.subdir(), "", Config::kConfigFileName, valFileName, false);
924 BEAST_EXPECT(vtg.validatorsFileExists());
925 BEAST_EXPECT(rcg.configFileExists());
926 auto const& c(rcg.config());
927 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == valFileName);
928 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8);
929 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
930 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2);
931 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
932 BEAST_EXPECT(c.validatorListThreshold == 2);
933 }
934 {
935 // load from specified [validators_file] relative path
936 // to config directory
937 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.txt");
938 auto const valFilePath = ".." / vtg.subdir() / "validators.txt";
939 detail::FileCfgGuard const rcg(
940 *this, vtg.subdir(), "", Config::kConfigFileName, valFilePath, false);
941 BEAST_EXPECT(vtg.validatorsFileExists());
942 BEAST_EXPECT(rcg.configFileExists());
943 auto const& c(rcg.config());
944 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == valFilePath);
945 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8);
946 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
947 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2);
948 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
949 BEAST_EXPECT(c.validatorListThreshold == 2);
950 }
951 {
952 // load from validators file in default location
953 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.txt");
954 detail::FileCfgGuard const rcg(
955 *this, vtg.subdir(), "", Config::kConfigFileName, "", false);
956 BEAST_EXPECT(vtg.validatorsFileExists());
957 BEAST_EXPECT(rcg.configFileExists());
958 auto const& c(rcg.config());
959 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile).empty());
960 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8);
961 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
962 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2);
963 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
964 BEAST_EXPECT(c.validatorListThreshold == 2);
965 }
966 {
967 // load from specified [validators_file] instead
968 // of default location
969 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
970 BEAST_EXPECT(vtg.validatorsFileExists());
971 detail::ValidatorsTxtGuard const vtgDefault(
972 *this, vtg.subdir(), "validators.txt", false);
973 BEAST_EXPECT(vtgDefault.validatorsFileExists());
974 detail::FileCfgGuard const rcg(
975 *this, vtg.subdir(), "", Config::kConfigFileName, vtg.validatorsFile(), false);
976 BEAST_EXPECT(rcg.configFileExists());
977 auto const& c(rcg.config());
978 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile());
979 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8);
980 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2);
981 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2);
982 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
983 BEAST_EXPECT(c.validatorListThreshold == 2);
984 }
985
986 {
987 // load validators from both config and validators file
988 constexpr char const* cc = R"xrpldConfig(
989[validators_file]
990{}
991
992[validators]
993n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
994n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
995n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
996n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
997n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
998
999[validator_keys]
1000nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
1001nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
1002
1003[validator_list_sites]
1004xrpl-validators.com
1005trust-these-validators.gov
1006
1007[validator_list_keys]
1008021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
1009)xrpldConfig";
1010 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
1011 BEAST_EXPECT(vtg.validatorsFileExists());
1012 Config c;
1013 c.loadFromString(std::format(cc, vtg.validatorsFile()));
1014 BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile());
1015 BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 15);
1016 BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 4);
1017 BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 3);
1018 BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1);
1019 BEAST_EXPECT(c.validatorListThreshold == 2);
1020 }
1021 {
1022 // load should throw if [validator_list_threshold] is present both
1023 // in xrpld.cfg and validators file
1024 constexpr char const* cc = R"xrpldConfig(
1025[validators_file]
1026{}
1027
1028[validator_list_threshold]
10291
1030)xrpldConfig";
1031 std::string error;
1032 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
1033 BEAST_EXPECT(vtg.validatorsFileExists());
1034 auto const expectedError =
1035 "Config section [validator_list_threshold] should contain "
1036 "single value only";
1037 try
1038 {
1039 Config c;
1040 c.loadFromString(std::format(cc, vtg.validatorsFile()));
1041 fail();
1042 }
1043 catch (std::runtime_error const& e)
1044 {
1045 error = e.what();
1046 }
1047 BEAST_EXPECT(error == expectedError);
1048 }
1049 {
1050 // load should throw if [validators], [validator_keys] and
1051 // [validator_list_keys] are missing from xrpld.cfg and
1052 // validators file
1053 Config const c;
1054 constexpr char const* cc = "[validators_file]\n{}\n";
1055 std::string error;
1056 detail::ValidatorsTxtGuard const vtg(*this, "test_cfg", "validators.cfg");
1057 BEAST_EXPECT(vtg.validatorsFileExists());
1058 auto const expectedError =
1059 "The file specified in [validators_file] does not contain a "
1060 "[validators], [validator_keys] or [validator_list_keys] "
1061 "section: " +
1062 vtg.validatorsFile();
1063 std::ofstream const o(vtg.validatorsFile());
1064 try
1065 {
1066 Config c2;
1067 c2.loadFromString(std::format(cc, vtg.validatorsFile()));
1068 }
1069 catch (std::runtime_error const& e)
1070 {
1071 error = e.what();
1072 }
1073 BEAST_EXPECT(error == expectedError);
1074 }
1075 }
1076
1077 void
1078 testSetup(bool explicitPath)
1080 detail::FileCfgGuard const cfg(
1081 *this, "testSetup", explicitPath ? "test_db" : "", Config::kConfigFileName, "");
1082 /* FileCfgGuard has a Config object that gets loaded on
1083 construction, but Config::setup is not reentrant, so we
1084 need a fresh config for every test case, so ignore it.
1085 */
1086 {
1087 Config config;
1088 config.setup(
1089 cfg.configFile(),
1090 /*bQuiet*/ false,
1091 /* bSilent */ false,
1092 /* bStandalone */ false);
1093 BEAST_EXPECT(!config.quiet());
1094 BEAST_EXPECT(!config.silent());
1095 BEAST_EXPECT(!config.standalone());
1096 BEAST_EXPECT(config.ledgerHistory == 256);
1097 BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty());
1099 {
1100 Config config;
1101 config.setup(
1102 cfg.configFile(),
1103 /*bQuiet*/ true,
1104 /* bSilent */ false,
1105 /* bStandalone */ false);
1106 BEAST_EXPECT(config.quiet());
1107 BEAST_EXPECT(!config.silent());
1108 BEAST_EXPECT(!config.standalone());
1109 BEAST_EXPECT(config.ledgerHistory == 256);
1110 BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty());
1111 }
1112 {
1113 Config config;
1114 config.setup(
1115 cfg.configFile(),
1116 /*bQuiet*/ false,
1117 /* bSilent */ true,
1118 /* bStandalone */ false);
1119 BEAST_EXPECT(config.quiet());
1120 BEAST_EXPECT(config.silent());
1121 BEAST_EXPECT(!config.standalone());
1122 BEAST_EXPECT(config.ledgerHistory == 256);
1123 BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty());
1124 }
1125 {
1126 Config config;
1127 config.setup(
1128 cfg.configFile(),
1129 /*bQuiet*/ true,
1130 /* bSilent */ true,
1131 /* bStandalone */ false);
1132 BEAST_EXPECT(config.quiet());
1133 BEAST_EXPECT(config.silent());
1134 BEAST_EXPECT(!config.standalone());
1135 BEAST_EXPECT(config.ledgerHistory == 256);
1136 BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty());
1137 }
1138 {
1139 Config config;
1140 config.setup(
1141 cfg.configFile(),
1142 /*bQuiet*/ false,
1143 /* bSilent */ false,
1144 /* bStandalone */ true);
1145 BEAST_EXPECT(!config.quiet());
1146 BEAST_EXPECT(!config.silent());
1147 BEAST_EXPECT(config.standalone());
1148 BEAST_EXPECT(config.ledgerHistory == 0);
1149 BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath);
1150 }
1151 {
1152 Config config;
1153 config.setup(
1154 cfg.configFile(),
1155 /*bQuiet*/ true,
1156 /* bSilent */ false,
1157 /* bStandalone */ true);
1158 BEAST_EXPECT(config.quiet());
1159 BEAST_EXPECT(!config.silent());
1160 BEAST_EXPECT(config.standalone());
1161 BEAST_EXPECT(config.ledgerHistory == 0);
1162 BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath);
1163 }
1164 {
1165 Config config;
1166 config.setup(
1167 cfg.configFile(),
1168 /*bQuiet*/ false,
1169 /* bSilent */ true,
1170 /* bStandalone */ true);
1171 BEAST_EXPECT(config.quiet());
1172 BEAST_EXPECT(config.silent());
1173 BEAST_EXPECT(config.standalone());
1174 BEAST_EXPECT(config.ledgerHistory == 0);
1175 BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath);
1176 }
1177 {
1178 Config config;
1179 config.setup(
1180 cfg.configFile(),
1181 /*bQuiet*/ true,
1182 /* bSilent */ true,
1183 /* bStandalone */ true);
1184 BEAST_EXPECT(config.quiet());
1185 BEAST_EXPECT(config.silent());
1186 BEAST_EXPECT(config.standalone());
1187 BEAST_EXPECT(config.ledgerHistory == 0);
1188 BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath);
1189 }
1190 }
1191
1192 void
1193 testPort()
1195 detail::FileCfgGuard const cfg(*this, "testPort", "", Config::kConfigFileName, "");
1196 auto const& conf = cfg.config();
1197 if (!BEAST_EXPECT(conf.exists(Sections::kPortRpc)))
1198 return;
1199 if (!BEAST_EXPECT(conf.exists(Sections::kPortWssAdmin)))
1200 return;
1202 if (!unexcept([&]() { parsePort(rpc, conf[Sections::kPortRpc], log); }))
1203 return;
1204 BEAST_EXPECT(rpc.adminNetsV4.size() + rpc.adminNetsV6.size() == 2);
1205 ParsedPort wss;
1206 if (!unexcept([&]() { parsePort(wss, conf[Sections::kPortWssAdmin], log); }))
1207 return;
1208 BEAST_EXPECT(wss.adminNetsV4.size() + wss.adminNetsV6.size() == 1);
1209 }
1210
1211 void
1212 testZeroPort()
1213 {
1214 auto const contents = std::regex_replace(
1215 detail::configContents("", ""), std::regex(R"(port\s*=\s*\d+)"), "port = 0");
1216
1217 try
1218 {
1219 detail::FileCfgGuard const cfg(
1220 *this, "testPort", "", Config::kConfigFileName, "", true, contents);
1221 BEAST_EXPECT(false);
1222 }
1223 catch (std::exception const& ex)
1224 {
1225 BEAST_EXPECT(
1226 std::string_view(ex.what()).starts_with("Invalid value '0' for key 'port'"));
1227 }
1228 }
1229
1230 void
1232 {
1233 Config cfg;
1234 /* NOTE: this string includes some explicit
1235 * space chars in order to verify proper trimming */
1236 std::string const toLoad(
1237 R"(
1238[port_rpc])"
1239 "\x20"
1240 R"(
1241# comment
1242 # indented comment
1243)"
1244 "\x20\x20"
1245 R"(
1246[ips])"
1247 "\x20"
1248 R"(
1249r.ripple.com 51235
1250
1251 [ips_fixed])"
1252 "\x20\x20"
1253 R"(
1254 # COMMENT
1255 s1.ripple.com 51235
1256 s2.ripple.com 51235
1257
1258)");
1259 cfg.loadFromString(toLoad);
1260 BEAST_EXPECT(
1261 cfg.exists(Sections::kPortRpc) && cfg.section(Sections::kPortRpc).lines().empty() &&
1262 cfg.section(Sections::kPortRpc).values().empty());
1263 BEAST_EXPECT(
1264 cfg.exists(Sections::kIps) && cfg.section(Sections::kIps).lines().size() == 1 &&
1265 cfg.section(Sections::kIps).values().size() == 1);
1266 BEAST_EXPECT(
1267 cfg.exists(Sections::kIpsFixed) &&
1268 cfg.section(Sections::kIpsFixed).lines().size() == 2 &&
1269 cfg.section(Sections::kIpsFixed).values().size() == 2);
1270 }
1271
1272 void
1273 testColons()
1274 {
1275 Config cfg;
1276 /* NOTE: this string includes some explicit
1277 * space chars in order to verify proper trimming */
1278 std::string const toLoad(
1279 R"(
1280[port_rpc])"
1281 "\x20"
1282 R"(
1283# comment
1284 # indented comment
1285)"
1286 "\x20\x20"
1287 R"(
1288[ips])"
1289 "\x20"
1290 R"(
1291r.ripple.com:51235
1292
1293 [ips_fixed])"
1294 "\x20\x20"
1295 R"(
1296 # COMMENT
1297 s1.ripple.com:51235
1298 s2.ripple.com 51235
1299 anotherserversansport
1300 anotherserverwithport:12
1301 1.1.1.1:1
1302 1.1.1.1 1
1303 12.34.12.123:12345
1304 12.34.12.123 12345
1305 ::
1306 2001:db8::
1307 ::1
1308 ::1:12345
1309 [::1]:12345
1310 2001:db8:3333:4444:5555:6666:7777:8888:12345
1311 [2001:db8:3333:4444:5555:6666:7777:8888]:1
1312
1313
1314)");
1315 cfg.loadFromString(toLoad);
1316 BEAST_EXPECT(
1317 cfg.exists(Sections::kPortRpc) && cfg.section(Sections::kPortRpc).lines().empty() &&
1318 cfg.section(Sections::kPortRpc).values().empty());
1319 BEAST_EXPECT(
1320 cfg.exists(Sections::kIps) && cfg.section(Sections::kIps).lines().size() == 1 &&
1321 cfg.section(Sections::kIps).values().size() == 1);
1322 BEAST_EXPECT(
1323 cfg.exists(Sections::kIpsFixed) &&
1324 cfg.section(Sections::kIpsFixed).lines().size() == 15 &&
1325 cfg.section(Sections::kIpsFixed).values().size() == 15);
1326 BEAST_EXPECT(cfg.ips[0] == "r.ripple.com 51235");
1327
1328 BEAST_EXPECT(cfg.ipsFixed[0] == "s1.ripple.com 51235");
1329 BEAST_EXPECT(cfg.ipsFixed[1] == "s2.ripple.com 51235");
1330 BEAST_EXPECT(cfg.ipsFixed[2] == "anotherserversansport");
1331 BEAST_EXPECT(cfg.ipsFixed[3] == "anotherserverwithport 12");
1332 BEAST_EXPECT(cfg.ipsFixed[4] == "1.1.1.1 1");
1333 BEAST_EXPECT(cfg.ipsFixed[5] == "1.1.1.1 1");
1334 BEAST_EXPECT(cfg.ipsFixed[6] == "12.34.12.123 12345");
1335 BEAST_EXPECT(cfg.ipsFixed[7] == "12.34.12.123 12345");
1336
1337 // all ipv6 should be ignored by colon replacer, howsoever formatted
1338 BEAST_EXPECT(cfg.ipsFixed[8] == "::");
1339 BEAST_EXPECT(cfg.ipsFixed[9] == "2001:db8::");
1340 BEAST_EXPECT(cfg.ipsFixed[10] == "::1");
1341 BEAST_EXPECT(cfg.ipsFixed[11] == "::1:12345");
1342 BEAST_EXPECT(cfg.ipsFixed[12] == "[::1]:12345");
1343 BEAST_EXPECT(cfg.ipsFixed[13] == "2001:db8:3333:4444:5555:6666:7777:8888:12345");
1344 BEAST_EXPECT(cfg.ipsFixed[14] == "[2001:db8:3333:4444:5555:6666:7777:8888]:1");
1345 }
1346
1347 void
1348 testComments()
1349 {
1350 struct TestCommentData
1351 {
1352 std::string_view line;
1353 std::string_view field;
1354 std::string_view expect;
1355 bool hadComment;
1356 };
1357
1358 std::array<TestCommentData, 13> const tests = {
1359 {{.line = "password = aaaa\\#bbbb",
1360 .field = "password",
1361 .expect = "aaaa#bbbb",
1362 .hadComment = false},
1363 {.line = "password = aaaa#bbbb",
1364 .field = "password",
1365 .expect = "aaaa",
1366 .hadComment = true},
1367 {.line = "password = aaaa #bbbb",
1368 .field = "password",
1369 .expect = "aaaa",
1370 .hadComment = true},
1371 // since the value is all comment, this doesn't parse as k=v :
1372 {.line = "password = #aaaa #bbbb",
1373 .field = "",
1374 .expect = "password =",
1375 .hadComment = true},
1376 {.line = "password = aaaa\\# #bbbb",
1377 .field = "password",
1378 .expect = "aaaa#",
1379 .hadComment = true},
1380 {.line = "password = aaaa\\##bbbb",
1381 .field = "password",
1382 .expect = "aaaa#",
1383 .hadComment = true},
1384 {.line = "aaaa#bbbb", .field = "", .expect = "aaaa", .hadComment = true},
1385 {.line = "aaaa\\#bbbb", .field = "", .expect = "aaaa#bbbb", .hadComment = false},
1386 {.line = "aaaa\\##bbbb", .field = "", .expect = "aaaa#", .hadComment = true},
1387 {.line = "aaaa #bbbb", .field = "", .expect = "aaaa", .hadComment = true},
1388 {.line = "1 #comment", .field = "", .expect = "1", .hadComment = true},
1389 {.line = "#whole thing is comment", .field = "", .expect = "", .hadComment = false},
1390 {.line = " #whole comment with space",
1391 .field = "",
1392 .expect = "",
1393 .hadComment = false}}};
1394
1395 for (auto const& t : tests)
1396 {
1397 Section s;
1398 s.append(std::string(t.line));
1399 BEAST_EXPECT(s.hadTrailingComments() == t.hadComment);
1400 if (t.field.empty())
1401 {
1402 BEAST_EXPECTS(s.legacy() == t.expect, s.legacy());
1403 }
1404 else
1405 {
1406 std::string field;
1407 BEAST_EXPECTS(set(field, std::string(t.field), s), t.line);
1408 BEAST_EXPECTS(field == t.expect, t.line);
1409 }
1410 }
1411
1412 {
1413 Section s;
1414 s.append("online_delete = 3000");
1415 std::uint32_t od = 0;
1416 BEAST_EXPECT(set(od, Keys::kOnlineDelete, s));
1417 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1418 BEAST_EXPECTS(od == 3000, *(s.get<std::string>(Keys::kOnlineDelete)));
1419 }
1420
1421 {
1422 Section s;
1423 s.append("online_delete = 2000 #my comment on this");
1424 std::uint32_t od = 0;
1425 BEAST_EXPECT(set(od, Keys::kOnlineDelete, s));
1426 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1427 BEAST_EXPECTS(od == 2000, *(s.get<std::string>(Keys::kOnlineDelete)));
1428 }
1430
1431 void
1432 testGetters()
1433 {
1434 using namespace std::string_literals;
1435 Section s{"MySection"};
1436 s.append("a_string = mystring");
1437 s.append("positive_int = 2");
1438 s.append("negative_int = -3");
1439 s.append("bool_ish = 1");
1440
1441 {
1442 auto val1 = "value 1"s;
1443 BEAST_EXPECT(set(val1, "a_string", s));
1444 BEAST_EXPECT(val1 == "mystring");
1445
1446 auto val2 = "value 2"s;
1447 BEAST_EXPECT(!set(val2, "not_a_key", s));
1448 BEAST_EXPECT(val2 == "value 2");
1449 BEAST_EXPECT(!set(val2, "default"s, "not_a_key", s));
1450 BEAST_EXPECT(val2 == "default");
1451
1452 auto val3 = get<std::string>(s, "a_string");
1453 BEAST_EXPECT(val3 == "mystring");
1454 auto val4 = get<std::string>(s, "not_a_key");
1455 BEAST_EXPECT(val4.empty());
1456 auto val5 = get<std::string>(s, "not_a_key", "default");
1457 BEAST_EXPECT(val5 == "default");
1458
1459 auto val6 = "value 6"s;
1460 BEAST_EXPECT(getIfExists(s, "a_string", val6));
1461 BEAST_EXPECT(val6 == "mystring");
1462
1463 auto val7 = "value 7"s;
1464 BEAST_EXPECT(!getIfExists(s, "not_a_key", val7));
1465 BEAST_EXPECT(val7 == "value 7");
1466 }
1467
1468 {
1469 int val1 = 1;
1470 BEAST_EXPECT(set(val1, "positive_int", s));
1471 BEAST_EXPECT(val1 == 2);
1472
1473 int val2 = 2;
1474 BEAST_EXPECT(set(val2, "negative_int", s));
1475 BEAST_EXPECT(val2 == -3);
1476
1477 int val3 = 3;
1478 BEAST_EXPECT(!set(val3, "a_string", s));
1479 BEAST_EXPECT(val3 == 3);
1480
1481 auto val4 = get<int>(s, "positive_int");
1482 BEAST_EXPECT(val4 == 2);
1483 auto val5 = get<int>(s, "not_a_key");
1484 BEAST_EXPECT(val5 == 0);
1485 auto val6 = get<int>(s, "not_a_key", 5);
1486 BEAST_EXPECT(val6 == 5);
1487 auto val7 = get<int>(s, "a_string", 6);
1488 BEAST_EXPECT(val7 == 6);
1489
1490 int val8 = 8;
1491 BEAST_EXPECT(getIfExists(s, "positive_int", val8));
1492 BEAST_EXPECT(val8 == 2);
1493
1494 auto val9 = 9;
1495 BEAST_EXPECT(!getIfExists(s, "not_a_key", val9));
1496 BEAST_EXPECT(val9 == 9);
1497
1498 auto val10 = 10;
1499 BEAST_EXPECT(!getIfExists(s, "a_string", val10));
1500 BEAST_EXPECT(val10 == 10);
1501
1502 BEAST_EXPECT(s.get<int>("not_a_key") == std::nullopt);
1503 try
1504 {
1505 [[maybe_unused]] auto _ = s.get<int>("a_string");
1506 fail();
1507 }
1508 catch (boost::bad_lexical_cast&)
1509 {
1510 pass();
1511 }
1512 }
1513
1514 {
1515 bool flag1 = false;
1516 BEAST_EXPECT(getIfExists(s, "bool_ish", flag1));
1517 BEAST_EXPECT(flag1 == true);
1518
1519 bool flag2 = false;
1520 BEAST_EXPECT(!getIfExists(s, "not_a_key", flag2));
1521 BEAST_EXPECT(flag2 == false);
1522 }
1523 }
1524
1525 void
1527 {
1528 testcase("amendment");
1529 struct ConfigUnit
1530 {
1531 std::string unit;
1532 std::uint32_t numSeconds;
1533 std::uint32_t configVal;
1534 bool shouldPass;
1535 };
1536
1537 std::vector<ConfigUnit> const units = {
1538 {.unit = "seconds", .numSeconds = 1, .configVal = 15 * 60, .shouldPass = false},
1539 {.unit = "minutes", .numSeconds = 60, .configVal = 14, .shouldPass = false},
1540 {.unit = "minutes", .numSeconds = 60, .configVal = 15, .shouldPass = true},
1541 {.unit = "hours", .numSeconds = 3600, .configVal = 10, .shouldPass = true},
1542 {.unit = "days", .numSeconds = 86400, .configVal = 10, .shouldPass = true},
1543 {.unit = "weeks", .numSeconds = 604800, .configVal = 2, .shouldPass = true},
1544 {.unit = "months", .numSeconds = 2592000, .configVal = 1, .shouldPass = false},
1545 {.unit = "years", .numSeconds = 31536000, .configVal = 1, .shouldPass = false}};
1546
1547 std::string space;
1548 for (auto& [unit, sec, val, shouldPass] : units)
1549 {
1550 Config c;
1551 std::string toLoad(R"xrpldConfig(
1552[amendment_majority_time]
1553)xrpldConfig");
1554 // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
1555 toLoad += std::to_string(val) + space + unit;
1556 space = space.empty() ? " " : "";
1557
1558 try
1559 {
1560 c.loadFromString(toLoad);
1561 if (shouldPass)
1562 {
1563 BEAST_EXPECT(c.amendmentMajorityTime.count() == val * sec);
1564 }
1565 else
1566 {
1567 fail();
1568 }
1569 }
1570 catch (std::runtime_error const&)
1571 {
1572 if (!shouldPass)
1573 {
1574 pass();
1575 }
1576 else
1577 {
1578 fail();
1579 }
1580 }
1581 }
1583
1584 void
1585 testOverlay()
1586 {
1587 testcase("overlay: unknown time");
1588
1589 auto testUnknown = [](std::string value) -> std::optional<std::chrono::seconds> {
1590 try
1591 {
1592 Config c;
1593 c.loadFromString("[overlay]\nmax_unknown_time=" + value);
1594 return c.maxUnknownTime;
1595 }
1596 catch (std::runtime_error const&)
1597 {
1598 return {};
1599 }
1600 };
1601
1602 // Failures
1603 BEAST_EXPECT(!testUnknown("none"));
1604 BEAST_EXPECT(!testUnknown("0.5"));
1605 BEAST_EXPECT(!testUnknown("180 seconds"));
1606 BEAST_EXPECT(!testUnknown("9 minutes"));
1607
1608 // Below lower bound
1609 BEAST_EXPECT(!testUnknown("299"));
1610
1611 // In bounds
1612 BEAST_EXPECT(testUnknown("300") == std::chrono::seconds{300});
1613 BEAST_EXPECT(testUnknown("301") == std::chrono::seconds{301});
1614 BEAST_EXPECT(testUnknown("1799") == std::chrono::seconds{1799});
1615 BEAST_EXPECT(testUnknown("1800") == std::chrono::seconds{1800});
1616
1617 // Above upper bound
1618 BEAST_EXPECT(!testUnknown("1801"));
1619
1620 testcase("overlay: diverged time");
1621
1622 // In bounds:
1623 auto testDiverged = [](std::string value) -> std::optional<std::chrono::seconds> {
1624 try
1625 {
1626 Config c;
1627 c.loadFromString("[overlay]\nmax_diverged_time=" + value);
1628 return c.maxDivergedTime;
1629 }
1630 catch (std::runtime_error const&)
1631 {
1632 return {};
1633 }
1634 };
1635
1636 // Failures
1637 BEAST_EXPECT(!testDiverged("none"));
1638 BEAST_EXPECT(!testDiverged("0.5"));
1639 BEAST_EXPECT(!testDiverged("180 seconds"));
1640 BEAST_EXPECT(!testDiverged("9 minutes"));
1641
1642 // Below lower bound
1643 BEAST_EXPECT(!testDiverged("0"));
1644 BEAST_EXPECT(!testDiverged("59"));
1645
1646 // In bounds
1647 BEAST_EXPECT(testDiverged("60") == std::chrono::seconds{60});
1648 BEAST_EXPECT(testDiverged("61") == std::chrono::seconds{61});
1649 BEAST_EXPECT(testDiverged("899") == std::chrono::seconds{899});
1650 BEAST_EXPECT(testDiverged("900") == std::chrono::seconds{900});
1651
1652 // Above upper bound
1653 BEAST_EXPECT(!testDiverged("901"));
1654
1655 testcase("overlay: manifest counts");
1656
1657 // Both keys share one range and one parse path, so exercise each
1658 // through the same helper.
1659 auto testCount = [](std::string const& key,
1660 std::string const& value) -> std::optional<std::size_t> {
1661 try
1662 {
1663 Config c;
1664 c.loadFromString("[overlay]\n" + key + "=" + value);
1665 return key == "max_trusted_count" ? c.maxTrustedCount : c.maxUntrustedCount;
1666 }
1667 catch (std::runtime_error const&)
1668 {
1669 return {};
1670 }
1671 };
1672
1673 for (auto const* key : {"max_untrusted_count", "max_trusted_count"})
1674 {
1675 // Failures. A bad value must surface as std::runtime_error, not
1676 // the std::bad_cast that the underlying parse throws.
1677 BEAST_EXPECT(!testCount(key, "none"));
1678 BEAST_EXPECT(!testCount(key, "0.5"));
1679 BEAST_EXPECT(!testCount(key, "400 manifests"));
1680 BEAST_EXPECT(!testCount(key, "-1"));
1681
1682 // Below lower bound
1683 BEAST_EXPECT(!testCount(key, "0"));
1684 BEAST_EXPECT(!testCount(key, "49"));
1685
1686 // In bounds
1687 BEAST_EXPECT(testCount(key, "50") == 50);
1688 BEAST_EXPECT(testCount(key, "51") == 51);
1689 BEAST_EXPECT(testCount(key, "300") == 300);
1690 BEAST_EXPECT(testCount(key, "400") == 400);
1691 BEAST_EXPECT(testCount(key, "999") == 999);
1692 BEAST_EXPECT(testCount(key, "1000") == 1000);
1693
1694 // Above upper bound
1695 BEAST_EXPECT(!testCount(key, "1001"));
1696 }
1697
1698 // Each key is independent: setting one leaves the other unset.
1699 {
1700 Config c;
1701 c.loadFromString("[overlay]\nmax_untrusted_count=500");
1702 BEAST_EXPECT(c.maxUntrustedCount == 500);
1703 BEAST_EXPECT(!c.maxTrustedCount);
1704 }
1705 {
1706 Config c;
1707 c.loadFromString("[overlay]\nmax_trusted_count=500");
1708 BEAST_EXPECT(c.maxTrustedCount == 500);
1709 BEAST_EXPECT(!c.maxUntrustedCount);
1710 }
1711
1712 // Both can be set together.
1713 {
1714 Config c;
1715 c.loadFromString("[overlay]\nmax_untrusted_count=250\nmax_trusted_count=750");
1716 BEAST_EXPECT(c.maxUntrustedCount == 250);
1717 BEAST_EXPECT(c.maxTrustedCount == 750);
1718 }
1719
1720 // Unset leaves no override, so the use sites fall back to the defaults.
1721 {
1722 Config c;
1723 c.loadFromString("[overlay]\nip_limit=64");
1724 BEAST_EXPECT(!c.maxUntrustedCount);
1725 BEAST_EXPECT(!c.maxTrustedCount);
1726 }
1727
1728 // No [overlay] section at all leaves both unset too.
1729 {
1730 Config c;
1731 c.loadFromString("");
1732 BEAST_EXPECT(!c.maxUntrustedCount);
1733 BEAST_EXPECT(!c.maxTrustedCount);
1734 }
1735 }
1736
1737 void
1738 run() override
1739 {
1740 testLegacy();
1742 testDbPath();
1745 testSetup(false);
1746 testSetup(true);
1747 testPort();
1748 testZeroPort();
1750 testColons();
1751 testComments();
1752 testGetters();
1753 testAmendment();
1754 testOverlay();
1755 testNetworkID();
1756 }
1757};
1758
1760
1761} // namespace xrpl
T append(T... args)
T c_str(T... args)
A testsuite class.
Definition suite.h:52
bool unexcept(F &&f, String const &reason)
Definition suite.h:499
void pass()
Record a successful test condition.
Definition suite.h:532
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
LogOs< char > log
Logging output stream.
Definition suite.h:150
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
void legacy(std::string const &section, std::string value)
Set a value that is not a key/value pair.
void testSetup(bool explicitPath)
void run() override
Runs the suite.
std::filesystem::path Path
static constexpr std::string_view kConfigLegacyName
bool standalone() const
static constexpr std::string_view kDatabaseDirName
static constexpr std::string_view kConfigFileName
void setup(std::string const &strConf, bool bQuiet, bool bSilent, bool bStandalone)
std::chrono::seconds maxUnknownTime
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
std::uint32_t ledgerHistory
Holds a collection of configuration values.
Definition BasicConfig.h:28
std::optional< T > get(std::string const &name) const
void append(std::vector< std::string > const &lines)
Append a set of lines to this section.
bool expectException(Functor f, std::string const &message="")
Definition TestSuite.h:77
auto rmDir(Path const &toRm)
beast::unit_test::Suite & test_
Path const & subdir() const
std::filesystem::path Path
Write an xrpld config file and remove when done.
Config const & config() const
std::string configFile() const
FileCfgGuard(beast::unit_test::Suite &test, Path subDir, Path const &dbPath, Path const &configFile, Path const &validatorsFile, bool useCounter=true, std::string confContents="")
Write a file in a directory and remove when done.
Path const & file() const
FileDirGuard(beast::unit_test::Suite &test, Path subDir, Path file, std::string const &contents, bool useCounter=true, bool create=true)
Write a validators.txt file and remove when done.
std::string validatorsFile() const
ValidatorsTxtGuard(beast::unit_test::Suite &test, Path subDir, Path const &validatorsFileName, bool useCounter=true)
T current_path(T... args)
T empty(T... args)
T endl(T... args)
T format(T... args)
T getenv(T... args)
T is_directory(T... args)
T move(T... args)
STL namespace.
std::string valFileContents()
std::string configContents(std::string const &dbPath, std::string const &validatorsFile)
API version numbers used in later API versions.
Definition ApiVersion.h:36
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
ApplyResult apply(ServiceRegistry &registry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:181
void parsePort(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:195
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool getIfExists(Section const &section, std::string const &name, T &v)
static std::string const & systemName()
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
static std::optional< Seed > validationSeed(json::Value const &params)
T regex_replace(T... args)
T size(T... args)
T starts_with(T... args)
static constexpr auto kOnlineDelete
Definition Constants.h:137
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:96
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:97
static constexpr auto kIps
Definition Constants.h:23
static constexpr auto kPortWssAdmin
Definition Constants.h:49
static constexpr auto kValidatorListThreshold
Definition Constants.h:72
static constexpr auto kValidators
Definition Constants.h:74
static constexpr auto kValidatorListKeys
Definition Constants.h:70
static constexpr auto kServer
Definition Constants.h:56
static constexpr auto kPortRpc
Definition Constants.h:47
static constexpr auto kSslVerify
Definition Constants.h:62
static constexpr auto kValidatorsFile
Definition Constants.h:75
static constexpr auto kIpsFixed
Definition Constants.h:24
static constexpr auto kDatabasePath
Definition Constants.h:13
static constexpr auto kDebugLogfile
Definition Constants.h:14
static constexpr auto kValidatorListSites
Definition Constants.h:71
T to_string(T... args)
T what(T... args)