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 
43 // The configurable node sizes are "tiny", "small", "medium", "large", "huge"
46  // FIXME: We should document each of these items, explaining exactly
47  // what
48  // they control and whether there exists an explicit config
49  // option that can be used to override the default.
50  {SizedItem::sweepInterval, {{10, 30, 60, 90, 120}}},
51  {SizedItem::treeCacheSize, {{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::hashNodeDBCache, {{4, 12, 24, 64, 128}}},
57  {SizedItem::txnDBCache, {{4, 12, 24, 64, 128}}},
58  {SizedItem::lgrDBCache, {{4, 8, 16, 32, 128}}},
59  {SizedItem::openFinalLimit, {{8, 16, 32, 64, 128}}},
60  {SizedItem::burstSize, {{4, 8, 16, 32, 48}}},
61  }};
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  {
368  PEERS_MAX = beast::lexicalCastThrow<std::size_t>(strTemp);
369  }
370  else
371  {
372  std::optional<std::size_t> peers_in_max{};
373  if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_))
374  {
375  peers_in_max = beast::lexicalCastThrow<std::size_t>(strTemp);
376  if (*peers_in_max > 1000)
377  Throw<std::runtime_error>(
378  "Invalid value specified in [" SECTION_PEERS_IN_MAX
379  "] section; the value must be less or equal than 1000");
380  }
381 
382  std::optional<std::size_t> peers_out_max{};
383  if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_))
384  {
385  peers_out_max = beast::lexicalCastThrow<std::size_t>(strTemp);
386  if (*peers_out_max < 10 || *peers_out_max > 1000)
387  Throw<std::runtime_error>(
388  "Invalid value specified in [" SECTION_PEERS_OUT_MAX
389  "] section; the value must be in range 10-1000");
390  }
391 
392  // if one section is configured then the other must be configured too
393  if ((peers_in_max && !peers_out_max) ||
394  (peers_out_max && !peers_in_max))
395  Throw<std::runtime_error>("Both sections [" SECTION_PEERS_IN_MAX
396  "]"
397  "and [" SECTION_PEERS_OUT_MAX
398  "] must be configured");
399 
400  if (peers_in_max && peers_out_max)
401  {
402  PEERS_IN_MAX = *peers_in_max;
403  PEERS_OUT_MAX = *peers_out_max;
404  }
405  }
406 
407  if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_))
408  {
409  if (boost::iequals(strTemp, "tiny"))
410  NODE_SIZE = 0;
411  else if (boost::iequals(strTemp, "small"))
412  NODE_SIZE = 1;
413  else if (boost::iequals(strTemp, "medium"))
414  NODE_SIZE = 2;
415  else if (boost::iequals(strTemp, "large"))
416  NODE_SIZE = 3;
417  else if (boost::iequals(strTemp, "huge"))
418  NODE_SIZE = 4;
419  else
420  NODE_SIZE = std::min<std::size_t>(
421  4, beast::lexicalCastThrow<std::size_t>(strTemp));
422  }
423 
424  if (getSingleSection(secConfig, SECTION_SIGNING_SUPPORT, strTemp, j_))
425  signingEnabled_ = beast::lexicalCastThrow<bool>(strTemp);
426 
427  if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_))
428  ELB_SUPPORT = beast::lexicalCastThrow<bool>(strTemp);
429 
430  if (getSingleSection(secConfig, SECTION_WEBSOCKET_PING_FREQ, strTemp, j_))
431  WEBSOCKET_PING_FREQ =
432  std::chrono::seconds{beast::lexicalCastThrow<int>(strTemp)};
433 
434  getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, SSL_VERIFY_FILE, j_);
435  getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, SSL_VERIFY_DIR, j_);
436 
437  if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_))
438  SSL_VERIFY = beast::lexicalCastThrow<bool>(strTemp);
439 
440  if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_))
441  {
442  if (boost::iequals(strTemp, "all"))
443  RELAY_UNTRUSTED_VALIDATIONS = true;
444  else if (boost::iequals(strTemp, "trusted"))
445  RELAY_UNTRUSTED_VALIDATIONS = false;
446  else
447  Throw<std::runtime_error>(
448  "Invalid value specified in [" SECTION_RELAY_VALIDATIONS
449  "] section");
450  }
451 
452  if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_))
453  {
454  if (boost::iequals(strTemp, "all"))
455  RELAY_UNTRUSTED_PROPOSALS = true;
456  else if (boost::iequals(strTemp, "trusted"))
457  RELAY_UNTRUSTED_PROPOSALS = false;
458  else
459  Throw<std::runtime_error>(
460  "Invalid value specified in [" SECTION_RELAY_PROPOSALS
461  "] section");
462  }
463 
464  if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN))
465  Throw<std::runtime_error>("Cannot have both [" SECTION_VALIDATION_SEED
466  "] and [" SECTION_VALIDATOR_TOKEN
467  "] config sections");
468 
469  if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
470  NETWORK_QUORUM = beast::lexicalCastThrow<std::size_t>(strTemp);
471 
472  if (getSingleSection(secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
473  FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
474 
475  if (getSingleSection(secConfig, SECTION_FEE_OWNER_RESERVE, strTemp, j_))
476  FEE_OWNER_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
477 
478  if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_))
479  FEE_DEFAULT = beast::lexicalCastThrow<std::uint64_t>(strTemp);
480 
481  if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_))
482  {
483  if (boost::iequals(strTemp, "full"))
484  LEDGER_HISTORY =
485  std::numeric_limits<decltype(LEDGER_HISTORY)>::max();
486  else if (boost::iequals(strTemp, "none"))
487  LEDGER_HISTORY = 0;
488  else
489  LEDGER_HISTORY = beast::lexicalCastThrow<std::uint32_t>(strTemp);
490  }
491 
492  if (getSingleSection(secConfig, SECTION_FETCH_DEPTH, strTemp, j_))
493  {
494  if (boost::iequals(strTemp, "none"))
495  FETCH_DEPTH = 0;
496  else if (boost::iequals(strTemp, "full"))
497  FETCH_DEPTH = std::numeric_limits<decltype(FETCH_DEPTH)>::max();
498  else
499  FETCH_DEPTH = beast::lexicalCastThrow<std::uint32_t>(strTemp);
500 
501  if (FETCH_DEPTH < 10)
502  FETCH_DEPTH = 10;
503  }
504 
505  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_))
506  PATH_SEARCH_OLD = beast::lexicalCastThrow<int>(strTemp);
507  if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_))
508  PATH_SEARCH = beast::lexicalCastThrow<int>(strTemp);
509  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_))
510  PATH_SEARCH_FAST = beast::lexicalCastThrow<int>(strTemp);
511  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
512  PATH_SEARCH_MAX = beast::lexicalCastThrow<int>(strTemp);
513 
514  if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
515  DEBUG_LOGFILE = strTemp;
516 
517  if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_))
518  WORKERS = beast::lexicalCastThrow<std::size_t>(strTemp);
519 
520  if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
521  COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
522 
523  if (exists(SECTION_REDUCE_RELAY))
524  {
525  auto sec = section(SECTION_REDUCE_RELAY);
526  REDUCE_RELAY_ENABLE = sec.value_or("enable", false);
527  REDUCE_RELAY_SQUELCH = sec.value_or("squelch", false);
528  }
529 
530  if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))
531  {
532  MAX_TRANSACTIONS = std::clamp(
533  beast::lexicalCastThrow<int>(strTemp),
534  MIN_JOB_QUEUE_TX,
535  MAX_JOB_QUEUE_TX);
536  }
537 
538  if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_))
539  {
540  if (!isProperlyFormedTomlDomain(strTemp))
541  {
542  Throw<std::runtime_error>(
543  "Invalid " SECTION_SERVER_DOMAIN
544  ": the domain name does not appear to meet the requirements.");
545  }
546 
547  SERVER_DOMAIN = strTemp;
548  }
549 
550  if (exists(SECTION_OVERLAY))
551  {
552  auto const sec = section(SECTION_OVERLAY);
553 
554  using namespace std::chrono;
555 
556  try
557  {
558  if (auto val = sec.get<std::string>("max_unknown_time"))
559  MAX_UNKNOWN_TIME =
560  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
561  }
562  catch (...)
563  {
564  Throw<std::runtime_error>(
565  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
566  ": must be of the form '<number>' representing seconds.");
567  }
568 
569  if (MAX_UNKNOWN_TIME < seconds{300} || MAX_UNKNOWN_TIME > seconds{1800})
570  Throw<std::runtime_error>(
571  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
572  ": the time must be between 300 and 1800 seconds, inclusive.");
573 
574  try
575  {
576  if (auto val = sec.get<std::string>("max_diverged_time"))
577  MAX_DIVERGED_TIME =
578  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
579  }
580  catch (...)
581  {
582  Throw<std::runtime_error>(
583  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
584  ": must be of the form '<number>' representing seconds.");
585  }
586 
587  if (MAX_DIVERGED_TIME < seconds{60} || MAX_DIVERGED_TIME > seconds{900})
588  {
589  Throw<std::runtime_error>(
590  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
591  ": the time must be between 60 and 900 seconds, inclusive.");
592  }
593  }
594 
595  if (getSingleSection(
596  secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
597  {
598  using namespace std::chrono;
599  boost::regex const re(
600  "^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
601  boost::smatch match;
602  if (!boost::regex_match(strTemp, match, re))
603  Throw<std::runtime_error>(
604  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
605  ", must be: [0-9]+ [minutes|hours|days|weeks]");
606 
608  beast::lexicalCastThrow<std::uint32_t>(match[1].str());
609 
610  if (boost::iequals(match[2], "minutes"))
611  AMENDMENT_MAJORITY_TIME = minutes(duration);
612  else if (boost::iequals(match[2], "hours"))
613  AMENDMENT_MAJORITY_TIME = hours(duration);
614  else if (boost::iequals(match[2], "days"))
615  AMENDMENT_MAJORITY_TIME = days(duration);
616  else if (boost::iequals(match[2], "weeks"))
617  AMENDMENT_MAJORITY_TIME = weeks(duration);
618 
619  if (AMENDMENT_MAJORITY_TIME < minutes(15))
620  Throw<std::runtime_error>(
621  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
622  ", the minimum amount of time an amendment must hold a "
623  "majority is 15 minutes");
624  }
625 
626  // Do not load trusted validator configuration for standalone mode
627  if (!RUN_STANDALONE)
628  {
629  // If a file was explicitly specified, then throw if the
630  // path is malformed or if the file does not exist or is
631  // not a file.
632  // If the specified file is not an absolute path, then look
633  // for it in the same directory as the config file.
634  // If no path was specified, then look for validators.txt
635  // in the same directory as the config file, but don't complain
636  // if we can't find it.
637  boost::filesystem::path validatorsFile;
638 
639  if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
640  {
641  validatorsFile = strTemp;
642 
643  if (validatorsFile.empty())
644  Throw<std::runtime_error>(
645  "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
646 
647  if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
648  validatorsFile = CONFIG_DIR / validatorsFile;
649 
650  if (!boost::filesystem::exists(validatorsFile))
651  Throw<std::runtime_error>(
652  "The file specified in [" SECTION_VALIDATORS_FILE
653  "] "
654  "does not exist: " +
655  validatorsFile.string());
656 
657  else if (
658  !boost::filesystem::is_regular_file(validatorsFile) &&
659  !boost::filesystem::is_symlink(validatorsFile))
660  Throw<std::runtime_error>(
661  "Invalid file specified in [" SECTION_VALIDATORS_FILE
662  "]: " +
663  validatorsFile.string());
664  }
665  else if (!CONFIG_DIR.empty())
666  {
667  validatorsFile = CONFIG_DIR / validatorsFileName;
668 
669  if (!validatorsFile.empty())
670  {
671  if (!boost::filesystem::exists(validatorsFile))
672  validatorsFile.clear();
673  else if (
674  !boost::filesystem::is_regular_file(validatorsFile) &&
675  !boost::filesystem::is_symlink(validatorsFile))
676  validatorsFile.clear();
677  }
678  }
679 
680  if (!validatorsFile.empty() &&
681  boost::filesystem::exists(validatorsFile) &&
682  (boost::filesystem::is_regular_file(validatorsFile) ||
683  boost::filesystem::is_symlink(validatorsFile)))
684  {
685  boost::system::error_code ec;
686  auto const data = getFileContents(ec, validatorsFile);
687  if (ec)
688  {
689  Throw<std::runtime_error>(
690  "Failed to read '" + validatorsFile.string() + "'." +
691  std::to_string(ec.value()) + ": " + ec.message());
692  }
693 
694  auto iniFile = parseIniFile(data, true);
695 
696  auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS);
697 
698  if (entries)
699  section(SECTION_VALIDATORS).append(*entries);
700 
701  auto valKeyEntries =
702  getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS);
703 
704  if (valKeyEntries)
705  section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries);
706 
707  auto valSiteEntries =
708  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES);
709 
710  if (valSiteEntries)
711  section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries);
712 
713  auto valListKeys =
714  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS);
715 
716  if (valListKeys)
717  section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
718 
719  if (!entries && !valKeyEntries && !valListKeys)
720  Throw<std::runtime_error>(
721  "The file specified in [" SECTION_VALIDATORS_FILE
722  "] "
723  "does not contain a [" SECTION_VALIDATORS
724  "], "
725  "[" SECTION_VALIDATOR_KEYS
726  "] or "
727  "[" SECTION_VALIDATOR_LIST_KEYS
728  "]"
729  " section: " +
730  validatorsFile.string());
731  }
732 
733  // Consolidate [validator_keys] and [validators]
734  section(SECTION_VALIDATORS)
735  .append(section(SECTION_VALIDATOR_KEYS).lines());
736 
737  if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
738  section(SECTION_VALIDATOR_LIST_KEYS).lines().empty())
739  {
740  Throw<std::runtime_error>(
741  "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
742  "] config section is missing");
743  }
744  }
745 
746  {
747  auto const part = section("features");
748  for (auto const& s : part.values())
749  {
750  if (auto const f = getRegisteredFeature(s))
751  features.insert(*f);
752  else
753  Throw<std::runtime_error>(
754  "Unknown feature: " + s + " in config file.");
755  }
756  }
757 
758  // This doesn't properly belong here, but check to make sure that the
759  // value specified for network_quorum is achievable:
760  {
761  auto pm = PEERS_MAX;
762 
763  // FIXME this apparently magic value is actually defined as a constant
764  // elsewhere (see defaultMaxPeers) but we handle this check here.
765  if (pm == 0)
766  pm = 21;
767 
768  if (NETWORK_QUORUM > pm)
769  {
770  Throw<std::runtime_error>(
771  "The minimum number of required peers (network_quorum) exceeds "
772  "the maximum number of allowed peers (peers_max)");
773  }
774  }
775 }
776 
777 boost::filesystem::path
778 Config::getDebugLogFile() const
779 {
780  auto log_file = DEBUG_LOGFILE;
781 
782  if (!log_file.empty() && !log_file.is_absolute())
783  {
784  // Unless an absolute path for the log file is specified, the
785  // path is relative to the config file directory.
786  log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
787  }
788 
789  if (!log_file.empty())
790  {
791  auto log_dir = log_file.parent_path();
792 
793  if (!boost::filesystem::is_directory(log_dir))
794  {
795  boost::system::error_code ec;
796  boost::filesystem::create_directories(log_dir, ec);
797 
798  // If we fail, we warn but continue so that the calling code can
799  // decide how to handle this situation.
800  if (ec)
801  {
802  std::cerr << "Unable to create log file path " << log_dir
803  << ": " << ec.message() << '\n';
804  }
805  }
806  }
807 
808  return log_file;
809 }
810 
811 int
812 Config::getValueFor(SizedItem item, boost::optional<std::size_t> node) const
813 {
814  auto const index = static_cast<std::underlying_type_t<SizedItem>>(item);
815  assert(index < sizedItems.size());
816  assert(!node || *node <= 4);
817  return sizedItems.at(index).second.at(node.value_or(NODE_SIZE));
818 }
819 
820 } // namespace ripple
ripple::SizedItem::openFinalLimit
@ openFinalLimit
ripple::sizedItems
constexpr std::array< std::pair< SizedItem, std::array< int, 5 > >, 11 > sizedItems
Definition: Config.cpp:45
fstream
std::string
STL class.
ripple::SizedItem
SizedItem
Definition: Config.h:48
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::hashNodeDBCache
@ hashNodeDBCache
iostream
ripple::SizedItem::ledgerFetch
@ ledgerFetch
algorithm
std::underlying_type_t
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
ripple::SizedItem::burstSize
@ burstSize
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
std::optional
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