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::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  {SizedItem::burstSize, {{4, 8, 16, 32, 48}}},
63  }};
64 
65 // Ensure that the order of entries in the table corresponds to the
66 // order of entries in the enum:
67 static_assert(
68  []() constexpr->bool {
69  std::underlying_type_t<SizedItem> idx = 0;
70 
71  for (auto const& i : sizedItems)
72  {
73  if (static_cast<std::underlying_type_t<SizedItem>>(i.first) != idx)
74  return false;
75 
76  ++idx;
77  }
78 
79  return true;
80  }(),
81  "Mismatch between sized item enum & array indices");
82 
83 //
84 // TODO: Check permissions on config file before using it.
85 //
86 
87 #define SECTION_DEFAULT_NAME ""
88 
90 parseIniFile(std::string const& strInput, const bool bTrim)
91 {
92  std::string strData(strInput);
94  IniFileSections secResult;
95 
96  // Convert DOS format to unix.
97  boost::algorithm::replace_all(strData, "\r\n", "\n");
98 
99  // Convert MacOS format to unix.
100  boost::algorithm::replace_all(strData, "\r", "\n");
101 
102  boost::algorithm::split(vLines, strData, boost::algorithm::is_any_of("\n"));
103 
104  // Set the default Section name.
105  std::string strSection = SECTION_DEFAULT_NAME;
106 
107  // Initialize the default Section.
108  secResult[strSection] = IniFileSections::mapped_type();
109 
110  // Parse each line.
111  for (auto& strValue : vLines)
112  {
113  if (bTrim)
114  boost::algorithm::trim(strValue);
115 
116  if (strValue.empty() || strValue[0] == '#')
117  {
118  // Blank line or comment, do nothing.
119  }
120  else if (strValue[0] == '[' && strValue[strValue.length() - 1] == ']')
121  {
122  // New Section.
123  strSection = strValue.substr(1, strValue.length() - 2);
124  secResult.emplace(strSection, IniFileSections::mapped_type{});
125  }
126  else
127  {
128  // Another line for Section.
129  if (!strValue.empty())
130  secResult[strSection].push_back(strValue);
131  }
132  }
133 
134  return secResult;
135 }
136 
137 IniFileSections::mapped_type*
138 getIniFileSection(IniFileSections& secSource, std::string const& strSection)
139 {
140  IniFileSections::iterator it;
141  IniFileSections::mapped_type* smtResult;
142  it = secSource.find(strSection);
143  if (it == secSource.end())
144  smtResult = nullptr;
145  else
146  smtResult = &(it->second);
147  return smtResult;
148 }
149 
150 bool
152  IniFileSections& secSource,
153  std::string const& strSection,
154  std::string& strValue,
155  beast::Journal j)
156 {
157  IniFileSections::mapped_type* pmtEntries =
158  getIniFileSection(secSource, strSection);
159  bool bSingle = pmtEntries && 1 == pmtEntries->size();
160 
161  if (bSingle)
162  {
163  strValue = (*pmtEntries)[0];
164  }
165  else if (pmtEntries)
166  {
167  JLOG(j.warn()) << boost::str(
168  boost::format("Section [%s]: requires 1 line not %d lines.") %
169  strSection % pmtEntries->size());
170  }
171 
172  return bSingle;
173 }
174 
175 //------------------------------------------------------------------------------
176 //
177 // Config (DEPRECATED)
178 //
179 //------------------------------------------------------------------------------
180 
181 char const* const Config::configFileName = "rippled.cfg";
182 char const* const Config::databaseDirName = "db";
183 char const* const Config::validatorsFileName = "validators.txt";
184 
185 static std::string
186 getEnvVar(char const* name)
187 {
188  std::string value;
189 
190  auto const v = getenv(name);
191 
192  if (v != nullptr)
193  value = v;
194 
195  return value;
196 }
197 
198 constexpr FeeUnit32 Config::TRANSACTION_FEE_BASE;
199 
200 void
201 Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
202 {
203  QUIET = bQuiet || bSilent;
204  SILENT = bSilent;
205  RUN_STANDALONE = bStandalone;
206 }
207 
208 void
209 Config::setup(
210  std::string const& strConf,
211  bool bQuiet,
212  bool bSilent,
213  bool bStandalone)
214 {
215  boost::filesystem::path dataDir;
216  std::string strDbPath, strConfFile;
217 
218  // Determine the config and data directories.
219  // If the config file is found in the current working
220  // directory, use the current working directory as the
221  // config directory and that with "db" as the data
222  // directory.
223 
224  setupControl(bQuiet, bSilent, bStandalone);
225 
226  strDbPath = databaseDirName;
227 
228  if (!strConf.empty())
229  strConfFile = strConf;
230  else
231  strConfFile = configFileName;
232 
233  if (!strConf.empty())
234  {
235  // --conf=<path> : everything is relative that file.
236  CONFIG_FILE = strConfFile;
237  CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE);
238  CONFIG_DIR.remove_filename();
239  dataDir = CONFIG_DIR / strDbPath;
240  }
241  else
242  {
243  CONFIG_DIR = boost::filesystem::current_path();
244  CONFIG_FILE = CONFIG_DIR / strConfFile;
245  dataDir = CONFIG_DIR / strDbPath;
246 
247  // Construct XDG config and data home.
248  // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
249  std::string strHome = getEnvVar("HOME");
250  std::string strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
251  std::string strXdgDataHome = getEnvVar("XDG_DATA_HOME");
252 
253  if (boost::filesystem::exists(CONFIG_FILE)
254  // Can we figure out XDG dirs?
255  || (strHome.empty() &&
256  (strXdgConfigHome.empty() || strXdgDataHome.empty())))
257  {
258  // Current working directory is fine, put dbs in a subdir.
259  }
260  else
261  {
262  if (strXdgConfigHome.empty())
263  {
264  // $XDG_CONFIG_HOME was not set, use default based on $HOME.
265  strXdgConfigHome = strHome + "/.config";
266  }
267 
268  if (strXdgDataHome.empty())
269  {
270  // $XDG_DATA_HOME was not set, use default based on $HOME.
271  strXdgDataHome = strHome + "/.local/share";
272  }
273 
274  CONFIG_DIR = strXdgConfigHome + "/" + systemName();
275  CONFIG_FILE = CONFIG_DIR / strConfFile;
276  dataDir = strXdgDataHome + "/" + systemName();
277 
278  if (!boost::filesystem::exists(CONFIG_FILE))
279  {
280  CONFIG_DIR = "/etc/opt/" + systemName();
281  CONFIG_FILE = CONFIG_DIR / strConfFile;
282  dataDir = "/var/opt/" + systemName();
283  }
284  }
285  }
286 
287  // Update default values
288  load();
289  {
290  // load() may have set a new value for the dataDir
291  std::string const dbPath(legacy("database_path"));
292  if (!dbPath.empty())
293  dataDir = boost::filesystem::path(dbPath);
294  else if (RUN_STANDALONE)
295  dataDir.clear();
296  }
297 
298  if (!dataDir.empty())
299  {
300  boost::system::error_code ec;
301  boost::filesystem::create_directories(dataDir, ec);
302 
303  if (ec)
304  Throw<std::runtime_error>(
305  boost::str(boost::format("Can not create %s") % dataDir));
306 
307  legacy("database_path", boost::filesystem::absolute(dataDir).string());
308  }
309 
310  HTTPClient::initializeSSLContext(*this, j_);
311 
312  if (RUN_STANDALONE)
313  LEDGER_HISTORY = 0;
314 }
315 
316 void
317 Config::load()
318 {
319  // NOTE: this writes to cerr because we want cout to be reserved
320  // for the writing of the json response (so that stdout can be part of a
321  // pipeline, for instance)
322  if (!QUIET)
323  std::cerr << "Loading: " << CONFIG_FILE << "\n";
324 
325  boost::system::error_code ec;
326  auto const fileContents = getFileContents(ec, CONFIG_FILE);
327 
328  if (ec)
329  {
330  std::cerr << "Failed to read '" << CONFIG_FILE << "'." << ec.value()
331  << ": " << ec.message() << std::endl;
332  return;
333  }
334 
335  loadFromString(fileContents);
336 }
337 
338 void
339 Config::loadFromString(std::string const& fileContents)
340 {
341  IniFileSections secConfig = parseIniFile(fileContents, true);
342 
343  build(secConfig);
344 
345  if (auto s = getIniFileSection(secConfig, SECTION_IPS))
346  IPS = *s;
347 
348  if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED))
349  IPS_FIXED = *s;
350 
351  if (auto s = getIniFileSection(secConfig, SECTION_SNTP))
352  SNTP_SERVERS = *s;
353 
354  {
355  std::string dbPath;
356  if (getSingleSection(secConfig, "database_path", dbPath, j_))
357  {
358  boost::filesystem::path p(dbPath);
359  legacy("database_path", boost::filesystem::absolute(p).string());
360  }
361  }
362 
363  std::string strTemp;
364 
365  if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_))
366  PEER_PRIVATE = beast::lexicalCastThrow<bool>(strTemp);
367 
368  if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_))
369  {
370  PEERS_MAX = beast::lexicalCastThrow<std::size_t>(strTemp);
371  }
372  else
373  {
374  std::optional<std::size_t> peers_in_max{};
375  if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_))
376  {
377  peers_in_max = beast::lexicalCastThrow<std::size_t>(strTemp);
378  if (*peers_in_max > 1000)
379  Throw<std::runtime_error>(
380  "Invalid value specified in [" SECTION_PEERS_IN_MAX
381  "] section; the value must be less or equal than 1000");
382  }
383 
384  std::optional<std::size_t> peers_out_max{};
385  if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_))
386  {
387  peers_out_max = beast::lexicalCastThrow<std::size_t>(strTemp);
388  if (*peers_out_max < 10 || *peers_out_max > 1000)
389  Throw<std::runtime_error>(
390  "Invalid value specified in [" SECTION_PEERS_OUT_MAX
391  "] section; the value must be in range 10-1000");
392  }
393 
394  // if one section is configured then the other must be configured too
395  if ((peers_in_max && !peers_out_max) ||
396  (peers_out_max && !peers_in_max))
397  Throw<std::runtime_error>("Both sections [" SECTION_PEERS_IN_MAX
398  "]"
399  "and [" SECTION_PEERS_OUT_MAX
400  "] must be configured");
401 
402  if (peers_in_max && peers_out_max)
403  {
404  PEERS_IN_MAX = *peers_in_max;
405  PEERS_OUT_MAX = *peers_out_max;
406  }
407  }
408 
409  if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_))
410  {
411  if (boost::iequals(strTemp, "tiny"))
412  NODE_SIZE = 0;
413  else if (boost::iequals(strTemp, "small"))
414  NODE_SIZE = 1;
415  else if (boost::iequals(strTemp, "medium"))
416  NODE_SIZE = 2;
417  else if (boost::iequals(strTemp, "large"))
418  NODE_SIZE = 3;
419  else if (boost::iequals(strTemp, "huge"))
420  NODE_SIZE = 4;
421  else
422  NODE_SIZE = std::min<std::size_t>(
423  4, beast::lexicalCastThrow<std::size_t>(strTemp));
424  }
425 
426  if (getSingleSection(secConfig, SECTION_SIGNING_SUPPORT, strTemp, j_))
427  signingEnabled_ = beast::lexicalCastThrow<bool>(strTemp);
428 
429  if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_))
430  ELB_SUPPORT = beast::lexicalCastThrow<bool>(strTemp);
431 
432  if (getSingleSection(secConfig, SECTION_WEBSOCKET_PING_FREQ, strTemp, j_))
433  WEBSOCKET_PING_FREQ =
434  std::chrono::seconds{beast::lexicalCastThrow<int>(strTemp)};
435 
436  getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, SSL_VERIFY_FILE, j_);
437  getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, SSL_VERIFY_DIR, j_);
438 
439  if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_))
440  SSL_VERIFY = beast::lexicalCastThrow<bool>(strTemp);
441 
442  if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_))
443  {
444  if (boost::iequals(strTemp, "all"))
445  RELAY_UNTRUSTED_VALIDATIONS = true;
446  else if (boost::iequals(strTemp, "trusted"))
447  RELAY_UNTRUSTED_VALIDATIONS = false;
448  else
449  Throw<std::runtime_error>(
450  "Invalid value specified in [" SECTION_RELAY_VALIDATIONS
451  "] section");
452  }
453 
454  if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_))
455  {
456  if (boost::iequals(strTemp, "all"))
457  RELAY_UNTRUSTED_PROPOSALS = true;
458  else if (boost::iequals(strTemp, "trusted"))
459  RELAY_UNTRUSTED_PROPOSALS = false;
460  else
461  Throw<std::runtime_error>(
462  "Invalid value specified in [" SECTION_RELAY_PROPOSALS
463  "] section");
464  }
465 
466  if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN))
467  Throw<std::runtime_error>("Cannot have both [" SECTION_VALIDATION_SEED
468  "] and [" SECTION_VALIDATOR_TOKEN
469  "] config sections");
470 
471  if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
472  NETWORK_QUORUM = beast::lexicalCastThrow<std::size_t>(strTemp);
473 
474  if (getSingleSection(secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
475  FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
476 
477  if (getSingleSection(secConfig, SECTION_FEE_OWNER_RESERVE, strTemp, j_))
478  FEE_OWNER_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
479 
480  if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_))
481  FEE_DEFAULT = beast::lexicalCastThrow<std::uint64_t>(strTemp);
482 
483  if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_))
484  {
485  if (boost::iequals(strTemp, "full"))
486  LEDGER_HISTORY =
487  std::numeric_limits<decltype(LEDGER_HISTORY)>::max();
488  else if (boost::iequals(strTemp, "none"))
489  LEDGER_HISTORY = 0;
490  else
491  LEDGER_HISTORY = beast::lexicalCastThrow<std::uint32_t>(strTemp);
492  }
493 
494  if (getSingleSection(secConfig, SECTION_FETCH_DEPTH, strTemp, j_))
495  {
496  if (boost::iequals(strTemp, "none"))
497  FETCH_DEPTH = 0;
498  else if (boost::iequals(strTemp, "full"))
499  FETCH_DEPTH = std::numeric_limits<decltype(FETCH_DEPTH)>::max();
500  else
501  FETCH_DEPTH = beast::lexicalCastThrow<std::uint32_t>(strTemp);
502 
503  if (FETCH_DEPTH < 10)
504  FETCH_DEPTH = 10;
505  }
506 
507  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_))
508  PATH_SEARCH_OLD = beast::lexicalCastThrow<int>(strTemp);
509  if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_))
510  PATH_SEARCH = beast::lexicalCastThrow<int>(strTemp);
511  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_))
512  PATH_SEARCH_FAST = beast::lexicalCastThrow<int>(strTemp);
513  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
514  PATH_SEARCH_MAX = beast::lexicalCastThrow<int>(strTemp);
515 
516  if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
517  DEBUG_LOGFILE = strTemp;
518 
519  if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_))
520  WORKERS = beast::lexicalCastThrow<std::size_t>(strTemp);
521 
522  if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
523  COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
524 
525  if (exists(SECTION_REDUCE_RELAY))
526  {
527  auto sec = section(SECTION_REDUCE_RELAY);
528  REDUCE_RELAY_ENABLE = sec.value_or("enable", false);
529  REDUCE_RELAY_SQUELCH = sec.value_or("squelch", false);
530  }
531 
532  if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))
533  {
534  MAX_TRANSACTIONS = std::clamp(
535  beast::lexicalCastThrow<int>(strTemp),
536  MIN_JOB_QUEUE_TX,
537  MAX_JOB_QUEUE_TX);
538  }
539 
540  if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_))
541  {
542  if (!isProperlyFormedTomlDomain(strTemp))
543  {
544  Throw<std::runtime_error>(
545  "Invalid " SECTION_SERVER_DOMAIN
546  ": the domain name does not appear to meet the requirements.");
547  }
548 
549  SERVER_DOMAIN = strTemp;
550  }
551 
552  if (exists(SECTION_OVERLAY))
553  {
554  auto const sec = section(SECTION_OVERLAY);
555 
556  using namespace std::chrono;
557 
558  try
559  {
560  if (auto val = sec.get<std::string>("max_unknown_time"))
561  MAX_UNKNOWN_TIME =
562  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
563  }
564  catch (...)
565  {
566  Throw<std::runtime_error>(
567  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
568  ": must be of the form '<number>' representing seconds.");
569  }
570 
571  if (MAX_UNKNOWN_TIME < seconds{300} || MAX_UNKNOWN_TIME > seconds{1800})
572  Throw<std::runtime_error>(
573  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
574  ": the time must be between 300 and 1800 seconds, inclusive.");
575 
576  try
577  {
578  if (auto val = sec.get<std::string>("max_diverged_time"))
579  MAX_DIVERGED_TIME =
580  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
581  }
582  catch (...)
583  {
584  Throw<std::runtime_error>(
585  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
586  ": must be of the form '<number>' representing seconds.");
587  }
588 
589  if (MAX_DIVERGED_TIME < seconds{60} || MAX_DIVERGED_TIME > seconds{900})
590  {
591  Throw<std::runtime_error>(
592  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
593  ": the time must be between 60 and 900 seconds, inclusive.");
594  }
595  }
596 
597  if (getSingleSection(
598  secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
599  {
600  using namespace std::chrono;
601  boost::regex const re(
602  "^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
603  boost::smatch match;
604  if (!boost::regex_match(strTemp, match, re))
605  Throw<std::runtime_error>(
606  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
607  ", must be: [0-9]+ [minutes|hours|days|weeks]");
608 
610  beast::lexicalCastThrow<std::uint32_t>(match[1].str());
611 
612  if (boost::iequals(match[2], "minutes"))
613  AMENDMENT_MAJORITY_TIME = minutes(duration);
614  else if (boost::iequals(match[2], "hours"))
615  AMENDMENT_MAJORITY_TIME = hours(duration);
616  else if (boost::iequals(match[2], "days"))
617  AMENDMENT_MAJORITY_TIME = days(duration);
618  else if (boost::iequals(match[2], "weeks"))
619  AMENDMENT_MAJORITY_TIME = weeks(duration);
620 
621  if (AMENDMENT_MAJORITY_TIME < minutes(15))
622  Throw<std::runtime_error>(
623  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
624  ", the minimum amount of time an amendment must hold a "
625  "majority is 15 minutes");
626  }
627 
628  // Do not load trusted validator configuration for standalone mode
629  if (!RUN_STANDALONE)
630  {
631  // If a file was explicitly specified, then throw if the
632  // path is malformed or if the file does not exist or is
633  // not a file.
634  // If the specified file is not an absolute path, then look
635  // for it in the same directory as the config file.
636  // If no path was specified, then look for validators.txt
637  // in the same directory as the config file, but don't complain
638  // if we can't find it.
639  boost::filesystem::path validatorsFile;
640 
641  if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
642  {
643  validatorsFile = strTemp;
644 
645  if (validatorsFile.empty())
646  Throw<std::runtime_error>(
647  "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
648 
649  if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
650  validatorsFile = CONFIG_DIR / validatorsFile;
651 
652  if (!boost::filesystem::exists(validatorsFile))
653  Throw<std::runtime_error>(
654  "The file specified in [" SECTION_VALIDATORS_FILE
655  "] "
656  "does not exist: " +
657  validatorsFile.string());
658 
659  else if (
660  !boost::filesystem::is_regular_file(validatorsFile) &&
661  !boost::filesystem::is_symlink(validatorsFile))
662  Throw<std::runtime_error>(
663  "Invalid file specified in [" SECTION_VALIDATORS_FILE
664  "]: " +
665  validatorsFile.string());
666  }
667  else if (!CONFIG_DIR.empty())
668  {
669  validatorsFile = CONFIG_DIR / validatorsFileName;
670 
671  if (!validatorsFile.empty())
672  {
673  if (!boost::filesystem::exists(validatorsFile))
674  validatorsFile.clear();
675  else if (
676  !boost::filesystem::is_regular_file(validatorsFile) &&
677  !boost::filesystem::is_symlink(validatorsFile))
678  validatorsFile.clear();
679  }
680  }
681 
682  if (!validatorsFile.empty() &&
683  boost::filesystem::exists(validatorsFile) &&
684  (boost::filesystem::is_regular_file(validatorsFile) ||
685  boost::filesystem::is_symlink(validatorsFile)))
686  {
687  boost::system::error_code ec;
688  auto const data = getFileContents(ec, validatorsFile);
689  if (ec)
690  {
691  Throw<std::runtime_error>(
692  "Failed to read '" + validatorsFile.string() + "'." +
693  std::to_string(ec.value()) + ": " + ec.message());
694  }
695 
696  auto iniFile = parseIniFile(data, true);
697 
698  auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS);
699 
700  if (entries)
701  section(SECTION_VALIDATORS).append(*entries);
702 
703  auto valKeyEntries =
704  getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS);
705 
706  if (valKeyEntries)
707  section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries);
708 
709  auto valSiteEntries =
710  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES);
711 
712  if (valSiteEntries)
713  section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries);
714 
715  auto valListKeys =
716  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS);
717 
718  if (valListKeys)
719  section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
720 
721  if (!entries && !valKeyEntries && !valListKeys)
722  Throw<std::runtime_error>(
723  "The file specified in [" SECTION_VALIDATORS_FILE
724  "] "
725  "does not contain a [" SECTION_VALIDATORS
726  "], "
727  "[" SECTION_VALIDATOR_KEYS
728  "] or "
729  "[" SECTION_VALIDATOR_LIST_KEYS
730  "]"
731  " section: " +
732  validatorsFile.string());
733  }
734 
735  // Consolidate [validator_keys] and [validators]
736  section(SECTION_VALIDATORS)
737  .append(section(SECTION_VALIDATOR_KEYS).lines());
738 
739  if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
740  section(SECTION_VALIDATOR_LIST_KEYS).lines().empty())
741  {
742  Throw<std::runtime_error>(
743  "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
744  "] config section is missing");
745  }
746  }
747 
748  {
749  auto const part = section("features");
750  for (auto const& s : part.values())
751  {
752  if (auto const f = getRegisteredFeature(s))
753  features.insert(*f);
754  else
755  Throw<std::runtime_error>(
756  "Unknown feature: " + s + " in config file.");
757  }
758  }
759 
760  // This doesn't properly belong here, but check to make sure that the
761  // value specified for network_quorum is achievable:
762  {
763  auto pm = PEERS_MAX;
764 
765  // FIXME this apparently magic value is actually defined as a constant
766  // elsewhere (see defaultMaxPeers) but we handle this check here.
767  if (pm == 0)
768  pm = 21;
769 
770  if (NETWORK_QUORUM > pm)
771  {
772  Throw<std::runtime_error>(
773  "The minimum number of required peers (network_quorum) exceeds "
774  "the maximum number of allowed peers (peers_max)");
775  }
776  }
777 }
778 
779 boost::filesystem::path
780 Config::getDebugLogFile() const
781 {
782  auto log_file = DEBUG_LOGFILE;
783 
784  if (!log_file.empty() && !log_file.is_absolute())
785  {
786  // Unless an absolute path for the log file is specified, the
787  // path is relative to the config file directory.
788  log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
789  }
790 
791  if (!log_file.empty())
792  {
793  auto log_dir = log_file.parent_path();
794 
795  if (!boost::filesystem::is_directory(log_dir))
796  {
797  boost::system::error_code ec;
798  boost::filesystem::create_directories(log_dir, ec);
799 
800  // If we fail, we warn but continue so that the calling code can
801  // decide how to handle this situation.
802  if (ec)
803  {
804  std::cerr << "Unable to create log file path " << log_dir
805  << ": " << ec.message() << '\n';
806  }
807  }
808  }
809 
810  return log_file;
811 }
812 
813 int
814 Config::getValueFor(SizedItem item, boost::optional<std::size_t> node) const
815 {
816  auto const index = static_cast<std::underlying_type_t<SizedItem>>(item);
817  assert(index < sizedItems.size());
818  assert(!node || *node <= 4);
819  return sizedItems.at(index).second.at(node.value_or(NODE_SIZE));
820 }
821 
822 } // 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
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:90
ripple::sizedItems
constexpr std::array< std::pair< SizedItem, std::array< int, 5 > >, 13 > sizedItems
Definition: Config.cpp:45
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:151
std::endl
T endl(T... args)
std::clamp
T clamp(T... args)
ripple::getEnvVar
static std::string getEnvVar(char const *name)
Definition: Config.cpp:186
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:138
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