rippled
Config.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/basics/FileUtilities.h>
21 #include <ripple/basics/Log.h>
22 #include <ripple/basics/StringUtilities.h>
23 #include <ripple/basics/contract.h>
24 #include <ripple/beast/core/LexicalCast.h>
25 #include <ripple/core/Config.h>
26 #include <ripple/core/ConfigSections.h>
27 #include <ripple/json/json_reader.h>
28 #include <ripple/net/HTTPClient.h>
29 #include <ripple/protocol/Feature.h>
30 #include <ripple/protocol/SystemParameters.h>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/beast/core/string.hpp>
33 #include <boost/format.hpp>
34 #include <boost/regex.hpp>
35 #include <boost/system/error_code.hpp>
36 #include <algorithm>
37 #include <fstream>
38 #include <iostream>
39 #include <iterator>
40 
41 namespace ripple {
42 
45  {// FIXME: We should document each of these items, explaining exactly
46  // what
47  // they control and whether there exists an explicit config
48  // option that can be used to override the default.
49  {SizedItem::sweepInterval, {{10, 30, 60, 90, 120}}},
51  {{128000, 256000, 512000, 768000, 2048000}}},
52  {SizedItem::treeCacheAge, {{30, 60, 90, 120, 900}}},
53  {SizedItem::ledgerSize, {{32, 128, 256, 384, 768}}},
54  {SizedItem::ledgerAge, {{30, 90, 180, 240, 900}}},
55  {SizedItem::ledgerFetch, {{2, 3, 4, 5, 8}}},
56  {SizedItem::nodeCacheSize, {{16384, 32768, 131072, 262144, 524288}}},
57  {SizedItem::nodeCacheAge, {{60, 90, 120, 900, 1800}}},
58  {SizedItem::hashNodeDBCache, {{4, 12, 24, 64, 128}}},
59  {SizedItem::txnDBCache, {{4, 12, 24, 64, 128}}},
60  {SizedItem::lgrDBCache, {{4, 8, 16, 32, 128}}},
61  {SizedItem::openFinalLimit, {{8, 16, 32, 64, 128}}}}};
62 
63 // Ensure that the order of entries in the table corresponds to the
64 // order of entries in the enum:
65 static_assert(
66  []() constexpr->bool {
67  std::underlying_type_t<SizedItem> idx = 0;
68 
69  for (auto const& i : sizedItems)
70  {
71  if (static_cast<std::underlying_type_t<SizedItem>>(i.first) != idx)
72  return false;
73 
74  ++idx;
75  }
76 
77  return true;
78  }(),
79  "Mismatch between sized item enum & array indices");
80 
81 //
82 // TODO: Check permissions on config file before using it.
83 //
84 
85 #define SECTION_DEFAULT_NAME ""
86 
88 parseIniFile(std::string const& strInput, const bool bTrim)
89 {
90  std::string strData(strInput);
92  IniFileSections secResult;
93 
94  // Convert DOS format to unix.
95  boost::algorithm::replace_all(strData, "\r\n", "\n");
96 
97  // Convert MacOS format to unix.
98  boost::algorithm::replace_all(strData, "\r", "\n");
99 
100  boost::algorithm::split(vLines, strData, boost::algorithm::is_any_of("\n"));
101 
102  // Set the default Section name.
103  std::string strSection = SECTION_DEFAULT_NAME;
104 
105  // Initialize the default Section.
106  secResult[strSection] = IniFileSections::mapped_type();
107 
108  // Parse each line.
109  for (auto& strValue : vLines)
110  {
111  if (bTrim)
112  boost::algorithm::trim(strValue);
113 
114  if (strValue.empty() || strValue[0] == '#')
115  {
116  // Blank line or comment, do nothing.
117  }
118  else if (strValue[0] == '[' && strValue[strValue.length() - 1] == ']')
119  {
120  // New Section.
121  strSection = strValue.substr(1, strValue.length() - 2);
122  secResult.emplace(strSection, IniFileSections::mapped_type{});
123  }
124  else
125  {
126  // Another line for Section.
127  if (!strValue.empty())
128  secResult[strSection].push_back(strValue);
129  }
130  }
131 
132  return secResult;
133 }
134 
135 IniFileSections::mapped_type*
136 getIniFileSection(IniFileSections& secSource, std::string const& strSection)
137 {
138  IniFileSections::iterator it;
139  IniFileSections::mapped_type* smtResult;
140  it = secSource.find(strSection);
141  if (it == secSource.end())
142  smtResult = nullptr;
143  else
144  smtResult = &(it->second);
145  return smtResult;
146 }
147 
148 bool
150  IniFileSections& secSource,
151  std::string const& strSection,
152  std::string& strValue,
153  beast::Journal j)
154 {
155  IniFileSections::mapped_type* pmtEntries =
156  getIniFileSection(secSource, strSection);
157  bool bSingle = pmtEntries && 1 == pmtEntries->size();
158 
159  if (bSingle)
160  {
161  strValue = (*pmtEntries)[0];
162  }
163  else if (pmtEntries)
164  {
165  JLOG(j.warn()) << boost::str(
166  boost::format("Section [%s]: requires 1 line not %d lines.") %
167  strSection % pmtEntries->size());
168  }
169 
170  return bSingle;
171 }
172 
173 //------------------------------------------------------------------------------
174 //
175 // Config (DEPRECATED)
176 //
177 //------------------------------------------------------------------------------
178 
179 char const* const Config::configFileName = "rippled.cfg";
180 char const* const Config::databaseDirName = "db";
181 char const* const Config::validatorsFileName = "validators.txt";
182 
183 static std::string
184 getEnvVar(char const* name)
185 {
186  std::string value;
187 
188  auto const v = getenv(name);
189 
190  if (v != nullptr)
191  value = v;
192 
193  return value;
194 }
195 
196 constexpr FeeUnit32 Config::TRANSACTION_FEE_BASE;
197 
198 void
199 Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
200 {
201  QUIET = bQuiet || bSilent;
202  SILENT = bSilent;
203  RUN_STANDALONE = bStandalone;
204 }
205 
206 void
207 Config::setup(
208  std::string const& strConf,
209  bool bQuiet,
210  bool bSilent,
211  bool bStandalone)
212 {
213  boost::filesystem::path dataDir;
214  std::string strDbPath, strConfFile;
215 
216  // Determine the config and data directories.
217  // If the config file is found in the current working
218  // directory, use the current working directory as the
219  // config directory and that with "db" as the data
220  // directory.
221 
222  setupControl(bQuiet, bSilent, bStandalone);
223 
224  strDbPath = databaseDirName;
225 
226  if (!strConf.empty())
227  strConfFile = strConf;
228  else
229  strConfFile = configFileName;
230 
231  if (!strConf.empty())
232  {
233  // --conf=<path> : everything is relative that file.
234  CONFIG_FILE = strConfFile;
235  CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE);
236  CONFIG_DIR.remove_filename();
237  dataDir = CONFIG_DIR / strDbPath;
238  }
239  else
240  {
241  CONFIG_DIR = boost::filesystem::current_path();
242  CONFIG_FILE = CONFIG_DIR / strConfFile;
243  dataDir = CONFIG_DIR / strDbPath;
244 
245  // Construct XDG config and data home.
246  // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
247  std::string strHome = getEnvVar("HOME");
248  std::string strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
249  std::string strXdgDataHome = getEnvVar("XDG_DATA_HOME");
250 
251  if (boost::filesystem::exists(CONFIG_FILE)
252  // Can we figure out XDG dirs?
253  || (strHome.empty() &&
254  (strXdgConfigHome.empty() || strXdgDataHome.empty())))
255  {
256  // Current working directory is fine, put dbs in a subdir.
257  }
258  else
259  {
260  if (strXdgConfigHome.empty())
261  {
262  // $XDG_CONFIG_HOME was not set, use default based on $HOME.
263  strXdgConfigHome = strHome + "/.config";
264  }
265 
266  if (strXdgDataHome.empty())
267  {
268  // $XDG_DATA_HOME was not set, use default based on $HOME.
269  strXdgDataHome = strHome + "/.local/share";
270  }
271 
272  CONFIG_DIR = strXdgConfigHome + "/" + systemName();
273  CONFIG_FILE = CONFIG_DIR / strConfFile;
274  dataDir = strXdgDataHome + "/" + systemName();
275 
276  if (!boost::filesystem::exists(CONFIG_FILE))
277  {
278  CONFIG_DIR = "/etc/opt/" + systemName();
279  CONFIG_FILE = CONFIG_DIR / strConfFile;
280  dataDir = "/var/opt/" + systemName();
281  }
282  }
283  }
284 
285  // Update default values
286  load();
287  {
288  // load() may have set a new value for the dataDir
289  std::string const dbPath(legacy("database_path"));
290  if (!dbPath.empty())
291  dataDir = boost::filesystem::path(dbPath);
292  else if (RUN_STANDALONE)
293  dataDir.clear();
294  }
295 
296  if (!dataDir.empty())
297  {
298  boost::system::error_code ec;
299  boost::filesystem::create_directories(dataDir, ec);
300 
301  if (ec)
302  Throw<std::runtime_error>(
303  boost::str(boost::format("Can not create %s") % dataDir));
304 
305  legacy("database_path", boost::filesystem::absolute(dataDir).string());
306  }
307 
308  HTTPClient::initializeSSLContext(*this, j_);
309 
310  if (RUN_STANDALONE)
311  LEDGER_HISTORY = 0;
312 }
313 
314 void
315 Config::load()
316 {
317  // NOTE: this writes to cerr because we want cout to be reserved
318  // for the writing of the json response (so that stdout can be part of a
319  // pipeline, for instance)
320  if (!QUIET)
321  std::cerr << "Loading: " << CONFIG_FILE << "\n";
322 
323  boost::system::error_code ec;
324  auto const fileContents = getFileContents(ec, CONFIG_FILE);
325 
326  if (ec)
327  {
328  std::cerr << "Failed to read '" << CONFIG_FILE << "'." << ec.value()
329  << ": " << ec.message() << std::endl;
330  return;
331  }
332 
333  loadFromString(fileContents);
334 }
335 
336 void
337 Config::loadFromString(std::string const& fileContents)
338 {
339  IniFileSections secConfig = parseIniFile(fileContents, true);
340 
341  build(secConfig);
342 
343  if (auto s = getIniFileSection(secConfig, SECTION_IPS))
344  IPS = *s;
345 
346  if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED))
347  IPS_FIXED = *s;
348 
349  if (auto s = getIniFileSection(secConfig, SECTION_SNTP))
350  SNTP_SERVERS = *s;
351 
352  {
353  std::string dbPath;
354  if (getSingleSection(secConfig, "database_path", dbPath, j_))
355  {
356  boost::filesystem::path p(dbPath);
357  legacy("database_path", boost::filesystem::absolute(p).string());
358  }
359  }
360 
361  std::string strTemp;
362 
363  if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_))
364  PEER_PRIVATE = beast::lexicalCastThrow<bool>(strTemp);
365 
366  if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_))
367  PEERS_MAX = beast::lexicalCastThrow<std::size_t>(strTemp);
368 
369  if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_))
370  {
371  if (boost::iequals(strTemp, "tiny"))
372  NODE_SIZE = 0;
373  else if (boost::iequals(strTemp, "small"))
374  NODE_SIZE = 1;
375  else if (boost::iequals(strTemp, "medium"))
376  NODE_SIZE = 2;
377  else if (boost::iequals(strTemp, "large"))
378  NODE_SIZE = 3;
379  else if (boost::iequals(strTemp, "huge"))
380  NODE_SIZE = 4;
381  else
382  NODE_SIZE = std::min<std::size_t>(
383  4, beast::lexicalCastThrow<std::size_t>(strTemp));
384  }
385 
386  if (getSingleSection(secConfig, SECTION_SIGNING_SUPPORT, strTemp, j_))
387  signingEnabled_ = beast::lexicalCastThrow<bool>(strTemp);
388 
389  if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_))
390  ELB_SUPPORT = beast::lexicalCastThrow<bool>(strTemp);
391 
392  if (getSingleSection(secConfig, SECTION_WEBSOCKET_PING_FREQ, strTemp, j_))
393  WEBSOCKET_PING_FREQ =
394  std::chrono::seconds{beast::lexicalCastThrow<int>(strTemp)};
395 
396  getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, SSL_VERIFY_FILE, j_);
397  getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, SSL_VERIFY_DIR, j_);
398 
399  if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_))
400  SSL_VERIFY = beast::lexicalCastThrow<bool>(strTemp);
401 
402  if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_))
403  {
404  if (boost::iequals(strTemp, "all"))
405  RELAY_UNTRUSTED_VALIDATIONS = true;
406  else if (boost::iequals(strTemp, "trusted"))
407  RELAY_UNTRUSTED_VALIDATIONS = false;
408  else
409  Throw<std::runtime_error>(
410  "Invalid value specified in [" SECTION_RELAY_VALIDATIONS
411  "] section");
412  }
413 
414  if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_))
415  {
416  if (boost::iequals(strTemp, "all"))
417  RELAY_UNTRUSTED_PROPOSALS = true;
418  else if (boost::iequals(strTemp, "trusted"))
419  RELAY_UNTRUSTED_PROPOSALS = false;
420  else
421  Throw<std::runtime_error>(
422  "Invalid value specified in [" SECTION_RELAY_PROPOSALS
423  "] section");
424  }
425 
426  if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN))
427  Throw<std::runtime_error>("Cannot have both [" SECTION_VALIDATION_SEED
428  "] "
429  "and [" SECTION_VALIDATOR_TOKEN
430  "] config sections");
431 
432  if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
433  NETWORK_QUORUM = beast::lexicalCastThrow<std::size_t>(strTemp);
434 
435  if (getSingleSection(secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
436  FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
437 
438  if (getSingleSection(secConfig, SECTION_FEE_OWNER_RESERVE, strTemp, j_))
439  FEE_OWNER_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
440 
441  if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_))
442  FEE_DEFAULT = beast::lexicalCastThrow<std::uint64_t>(strTemp);
443 
444  if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_))
445  {
446  if (boost::iequals(strTemp, "full"))
447  LEDGER_HISTORY =
448  std::numeric_limits<decltype(LEDGER_HISTORY)>::max();
449  else if (boost::iequals(strTemp, "none"))
450  LEDGER_HISTORY = 0;
451  else
452  LEDGER_HISTORY = beast::lexicalCastThrow<std::uint32_t>(strTemp);
453  }
454 
455  if (getSingleSection(secConfig, SECTION_FETCH_DEPTH, strTemp, j_))
456  {
457  if (boost::iequals(strTemp, "none"))
458  FETCH_DEPTH = 0;
459  else if (boost::iequals(strTemp, "full"))
460  FETCH_DEPTH = std::numeric_limits<decltype(FETCH_DEPTH)>::max();
461  else
462  FETCH_DEPTH = beast::lexicalCastThrow<std::uint32_t>(strTemp);
463 
464  if (FETCH_DEPTH < 10)
465  FETCH_DEPTH = 10;
466  }
467 
468  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_))
469  PATH_SEARCH_OLD = beast::lexicalCastThrow<int>(strTemp);
470  if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_))
471  PATH_SEARCH = beast::lexicalCastThrow<int>(strTemp);
472  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_))
473  PATH_SEARCH_FAST = beast::lexicalCastThrow<int>(strTemp);
474  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
475  PATH_SEARCH_MAX = beast::lexicalCastThrow<int>(strTemp);
476 
477  if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
478  DEBUG_LOGFILE = strTemp;
479 
480  if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_))
481  WORKERS = beast::lexicalCastThrow<std::size_t>(strTemp);
482 
483  if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
484  COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
485 
486  if (exists(SECTION_REDUCE_RELAY))
487  {
488  auto sec = section(SECTION_REDUCE_RELAY);
489  REDUCE_RELAY_ENABLE = sec.value_or("enable", false);
490  REDUCE_RELAY_SQUELCH = sec.value_or("squelch", false);
491  }
492 
493  if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))
494  {
495  MAX_TRANSACTIONS = std::clamp(
496  beast::lexicalCastThrow<int>(strTemp),
497  MIN_JOB_QUEUE_TX,
498  MAX_JOB_QUEUE_TX);
499  }
500 
501  if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_))
502  {
503  if (!isProperlyFormedTomlDomain(strTemp))
504  {
505  Throw<std::runtime_error>(
506  "Invalid " SECTION_SERVER_DOMAIN
507  ": the domain name does not appear to meet the requirements.");
508  }
509 
510  SERVER_DOMAIN = strTemp;
511  }
512 
513  if (getSingleSection(
514  secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
515  {
516  using namespace std::chrono;
517  boost::regex const re(
518  "^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
519  boost::smatch match;
520  if (!boost::regex_match(strTemp, match, re))
521  Throw<std::runtime_error>(
522  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
523  ", must be: [0-9]+ [minutes|hours|days|weeks]");
524 
526  beast::lexicalCastThrow<std::uint32_t>(match[1].str());
527 
528  if (boost::iequals(match[2], "minutes"))
529  AMENDMENT_MAJORITY_TIME = minutes(duration);
530  else if (boost::iequals(match[2], "hours"))
531  AMENDMENT_MAJORITY_TIME = hours(duration);
532  else if (boost::iequals(match[2], "days"))
533  AMENDMENT_MAJORITY_TIME = days(duration);
534  else if (boost::iequals(match[2], "weeks"))
535  AMENDMENT_MAJORITY_TIME = weeks(duration);
536 
537  if (AMENDMENT_MAJORITY_TIME < minutes(15))
538  Throw<std::runtime_error>(
539  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
540  ", the minimum amount of time an amendment must hold a "
541  "majority is 15 minutes");
542  }
543 
544  // Do not load trusted validator configuration for standalone mode
545  if (!RUN_STANDALONE)
546  {
547  // If a file was explicitly specified, then throw if the
548  // path is malformed or if the file does not exist or is
549  // not a file.
550  // If the specified file is not an absolute path, then look
551  // for it in the same directory as the config file.
552  // If no path was specified, then look for validators.txt
553  // in the same directory as the config file, but don't complain
554  // if we can't find it.
555  boost::filesystem::path validatorsFile;
556 
557  if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
558  {
559  validatorsFile = strTemp;
560 
561  if (validatorsFile.empty())
562  Throw<std::runtime_error>(
563  "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
564 
565  if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
566  validatorsFile = CONFIG_DIR / validatorsFile;
567 
568  if (!boost::filesystem::exists(validatorsFile))
569  Throw<std::runtime_error>(
570  "The file specified in [" SECTION_VALIDATORS_FILE
571  "] "
572  "does not exist: " +
573  validatorsFile.string());
574 
575  else if (
576  !boost::filesystem::is_regular_file(validatorsFile) &&
577  !boost::filesystem::is_symlink(validatorsFile))
578  Throw<std::runtime_error>(
579  "Invalid file specified in [" SECTION_VALIDATORS_FILE
580  "]: " +
581  validatorsFile.string());
582  }
583  else if (!CONFIG_DIR.empty())
584  {
585  validatorsFile = CONFIG_DIR / validatorsFileName;
586 
587  if (!validatorsFile.empty())
588  {
589  if (!boost::filesystem::exists(validatorsFile))
590  validatorsFile.clear();
591  else if (
592  !boost::filesystem::is_regular_file(validatorsFile) &&
593  !boost::filesystem::is_symlink(validatorsFile))
594  validatorsFile.clear();
595  }
596  }
597 
598  if (!validatorsFile.empty() &&
599  boost::filesystem::exists(validatorsFile) &&
600  (boost::filesystem::is_regular_file(validatorsFile) ||
601  boost::filesystem::is_symlink(validatorsFile)))
602  {
603  boost::system::error_code ec;
604  auto const data = getFileContents(ec, validatorsFile);
605  if (ec)
606  {
607  Throw<std::runtime_error>(
608  "Failed to read '" + validatorsFile.string() + "'." +
609  std::to_string(ec.value()) + ": " + ec.message());
610  }
611 
612  auto iniFile = parseIniFile(data, true);
613 
614  auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS);
615 
616  if (entries)
617  section(SECTION_VALIDATORS).append(*entries);
618 
619  auto valKeyEntries =
620  getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS);
621 
622  if (valKeyEntries)
623  section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries);
624 
625  auto valSiteEntries =
626  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES);
627 
628  if (valSiteEntries)
629  section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries);
630 
631  auto valListKeys =
632  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS);
633 
634  if (valListKeys)
635  section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
636 
637  if (!entries && !valKeyEntries && !valListKeys)
638  Throw<std::runtime_error>(
639  "The file specified in [" SECTION_VALIDATORS_FILE
640  "] "
641  "does not contain a [" SECTION_VALIDATORS
642  "], "
643  "[" SECTION_VALIDATOR_KEYS
644  "] or "
645  "[" SECTION_VALIDATOR_LIST_KEYS
646  "]"
647  " section: " +
648  validatorsFile.string());
649  }
650 
651  // Consolidate [validator_keys] and [validators]
652  section(SECTION_VALIDATORS)
653  .append(section(SECTION_VALIDATOR_KEYS).lines());
654 
655  if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
656  section(SECTION_VALIDATOR_LIST_KEYS).lines().empty())
657  {
658  Throw<std::runtime_error>(
659  "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
660  "] config section is missing");
661  }
662  }
663 
664  {
665  auto const part = section("features");
666  for (auto const& s : part.values())
667  {
668  if (auto const f = getRegisteredFeature(s))
669  features.insert(*f);
670  else
671  Throw<std::runtime_error>(
672  "Unknown feature: " + s + " in config file.");
673  }
674  }
675 
676  // This doesn't properly belong here, but check to make sure that the
677  // value specified for network_quorum is achievable:
678  {
679  auto pm = PEERS_MAX;
680 
681  // FIXME this apparently magic value is actually defined as a constant
682  // elsewhere (see defaultMaxPeers) but we handle this check here.
683  if (pm == 0)
684  pm = 21;
685 
686  if (NETWORK_QUORUM > pm)
687  {
688  Throw<std::runtime_error>(
689  "The minimum number of required peers (network_quorum) exceeds "
690  "the maximum number of allowed peers (peers_max)");
691  }
692  }
693 }
694 
695 boost::filesystem::path
696 Config::getDebugLogFile() const
697 {
698  auto log_file = DEBUG_LOGFILE;
699 
700  if (!log_file.empty() && !log_file.is_absolute())
701  {
702  // Unless an absolute path for the log file is specified, the
703  // path is relative to the config file directory.
704  log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
705  }
706 
707  if (!log_file.empty())
708  {
709  auto log_dir = log_file.parent_path();
710 
711  if (!boost::filesystem::is_directory(log_dir))
712  {
713  boost::system::error_code ec;
714  boost::filesystem::create_directories(log_dir, ec);
715 
716  // If we fail, we warn but continue so that the calling code can
717  // decide how to handle this situation.
718  if (ec)
719  {
720  std::cerr << "Unable to create log file path " << log_dir
721  << ": " << ec.message() << '\n';
722  }
723  }
724  }
725 
726  return log_file;
727 }
728 
729 int
730 Config::getValueFor(SizedItem item, boost::optional<std::size_t> node) const
731 {
732  auto const index = static_cast<std::underlying_type_t<SizedItem>>(item);
733  assert(index < sizedItems.size());
734  assert(!node || *node <= 4);
735  return sizedItems.at(index).second.at(node.value_or(NODE_SIZE));
736 }
737 
738 } // namespace ripple
ripple::SizedItem::openFinalLimit
@ openFinalLimit
fstream
std::string
STL class.
ripple::SizedItem
SizedItem
Definition: Config.h:48
ripple::SizedItem::nodeCacheSize
@ nodeCacheSize
std::vector< std::string >
std::map::find
T find(T... args)
std::string::size
T size(T... args)
std::chrono::seconds
iterator
std::map::emplace
T emplace(T... args)
ripple::SizedItem::treeCacheAge
@ treeCacheAge
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::cerr
ripple::SizedItem::nodeCacheAge
@ nodeCacheAge
ripple::SizedItem::hashNodeDBCache
@ hashNodeDBCache
iostream
ripple::SizedItem::ledgerFetch
@ ledgerFetch
algorithm
std::underlying_type_t
ripple::sizedItems
constexpr std::array< std::pair< SizedItem, std::array< int, 5 > >, 12 > sizedItems
Definition: Config.cpp:44
std::to_string
T to_string(T... args)
std::array
STL class.
ripple::SizedItem::lgrDBCache
@ lgrDBCache
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
std::map
STL class.
ripple::getFileContents
std::string getFileContents(boost::system::error_code &ec, boost::filesystem::path const &sourcePath, boost::optional< std::size_t > maxSize=boost::none)
Definition: FileUtilities.cpp:25
ripple::SizedItem::txnDBCache
@ txnDBCache
ripple::parseIniFile
IniFileSections parseIniFile(std::string const &strInput, const bool bTrim)
Definition: Config.cpp:88
std::string::substr
T substr(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::getSingleSection
bool getSingleSection(IniFileSections &secSource, std::string const &strSection, std::string &strValue, beast::Journal j)
Definition: Config.cpp:149
std::endl
T endl(T... args)
std::clamp
T clamp(T... args)
ripple::getEnvVar
static std::string getEnvVar(char const *name)
Definition: Config.cpp:184
ripple::SizedItem::treeCacheSize
@ treeCacheSize
ripple::SizedItem::sweepInterval
@ sweepInterval
std::string::empty
T empty(T... args)
ripple::SizedItem::ledgerSize
@ ledgerSize
ripple::isProperlyFormedTomlDomain
bool isProperlyFormedTomlDomain(std::string const &domain)
Determines if the given string looks like a TOML-file hosting domain.
std::map::end
T end(T... args)
std::numeric_limits
ripple::getIniFileSection
IniFileSections::mapped_type * getIniFileSection(IniFileSections &secSource, std::string const &strSection)
Definition: Config.cpp:136
ripple::SizedItem::ledgerAge
@ ledgerAge
ripple::IniFileSections
std::map< std::string, std::vector< std::string > > IniFileSections
Definition: BasicConfig.h:36
ripple::getRegisteredFeature
boost::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:143
std::chrono