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  if (exists("reporting"))
288  {
289  RUN_REPORTING = true;
290  RUN_STANDALONE = true;
291  }
292  {
293  // load() may have set a new value for the dataDir
294  std::string const dbPath(legacy("database_path"));
295  if (!dbPath.empty())
296  dataDir = boost::filesystem::path(dbPath);
297  else if (RUN_STANDALONE)
298  dataDir.clear();
299  }
300 
301  if (!dataDir.empty())
302  {
303  boost::system::error_code ec;
304  boost::filesystem::create_directories(dataDir, ec);
305 
306  if (ec)
307  Throw<std::runtime_error>(
308  boost::str(boost::format("Can not create %s") % dataDir));
309 
310  legacy("database_path", boost::filesystem::absolute(dataDir).string());
311  }
312 
313  HTTPClient::initializeSSLContext(*this, j_);
314 
315  if (RUN_STANDALONE)
316  LEDGER_HISTORY = 0;
317 
318  std::string ledgerTxDbType;
319  Section ledgerTxTablesSection = section("ledger_tx_tables");
320  get_if_exists(ledgerTxTablesSection, "use_tx_tables", USE_TX_TABLES);
321 }
322 
323 void
324 Config::load()
325 {
326  // NOTE: this writes to cerr because we want cout to be reserved
327  // for the writing of the json response (so that stdout can be part of a
328  // pipeline, for instance)
329  if (!QUIET)
330  std::cerr << "Loading: " << CONFIG_FILE << "\n";
331 
332  boost::system::error_code ec;
333  auto const fileContents = getFileContents(ec, CONFIG_FILE);
334 
335  if (ec)
336  {
337  std::cerr << "Failed to read '" << CONFIG_FILE << "'." << ec.value()
338  << ": " << ec.message() << std::endl;
339  return;
340  }
341 
342  loadFromString(fileContents);
343 }
344 
345 void
346 Config::loadFromString(std::string const& fileContents)
347 {
348  IniFileSections secConfig = parseIniFile(fileContents, true);
349 
350  build(secConfig);
351 
352  if (auto s = getIniFileSection(secConfig, SECTION_IPS))
353  IPS = *s;
354 
355  if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED))
356  IPS_FIXED = *s;
357 
358  if (auto s = getIniFileSection(secConfig, SECTION_SNTP))
359  SNTP_SERVERS = *s;
360 
361  {
362  std::string dbPath;
363  if (getSingleSection(secConfig, "database_path", dbPath, j_))
364  {
365  boost::filesystem::path p(dbPath);
366  legacy("database_path", boost::filesystem::absolute(p).string());
367  }
368  }
369 
370  std::string strTemp;
371 
372  if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_))
373  PEER_PRIVATE = beast::lexicalCastThrow<bool>(strTemp);
374 
375  if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_))
376  {
377  PEERS_MAX = beast::lexicalCastThrow<std::size_t>(strTemp);
378  }
379  else
380  {
381  std::optional<std::size_t> peers_in_max{};
382  if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_))
383  {
384  peers_in_max = beast::lexicalCastThrow<std::size_t>(strTemp);
385  if (*peers_in_max > 1000)
386  Throw<std::runtime_error>(
387  "Invalid value specified in [" SECTION_PEERS_IN_MAX
388  "] section; the value must be less or equal than 1000");
389  }
390 
391  std::optional<std::size_t> peers_out_max{};
392  if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_))
393  {
394  peers_out_max = beast::lexicalCastThrow<std::size_t>(strTemp);
395  if (*peers_out_max < 10 || *peers_out_max > 1000)
396  Throw<std::runtime_error>(
397  "Invalid value specified in [" SECTION_PEERS_OUT_MAX
398  "] section; the value must be in range 10-1000");
399  }
400 
401  // if one section is configured then the other must be configured too
402  if ((peers_in_max && !peers_out_max) ||
403  (peers_out_max && !peers_in_max))
404  Throw<std::runtime_error>("Both sections [" SECTION_PEERS_IN_MAX
405  "]"
406  "and [" SECTION_PEERS_OUT_MAX
407  "] must be configured");
408 
409  if (peers_in_max && peers_out_max)
410  {
411  PEERS_IN_MAX = *peers_in_max;
412  PEERS_OUT_MAX = *peers_out_max;
413  }
414  }
415 
416  if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_))
417  {
418  if (boost::iequals(strTemp, "tiny"))
419  NODE_SIZE = 0;
420  else if (boost::iequals(strTemp, "small"))
421  NODE_SIZE = 1;
422  else if (boost::iequals(strTemp, "medium"))
423  NODE_SIZE = 2;
424  else if (boost::iequals(strTemp, "large"))
425  NODE_SIZE = 3;
426  else if (boost::iequals(strTemp, "huge"))
427  NODE_SIZE = 4;
428  else
429  NODE_SIZE = std::min<std::size_t>(
430  4, beast::lexicalCastThrow<std::size_t>(strTemp));
431  }
432 
433  if (getSingleSection(secConfig, SECTION_SIGNING_SUPPORT, strTemp, j_))
434  signingEnabled_ = beast::lexicalCastThrow<bool>(strTemp);
435 
436  if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_))
437  ELB_SUPPORT = beast::lexicalCastThrow<bool>(strTemp);
438 
439  if (getSingleSection(secConfig, SECTION_WEBSOCKET_PING_FREQ, strTemp, j_))
440  WEBSOCKET_PING_FREQ =
441  std::chrono::seconds{beast::lexicalCastThrow<int>(strTemp)};
442 
443  getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, SSL_VERIFY_FILE, j_);
444  getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, SSL_VERIFY_DIR, j_);
445 
446  if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_))
447  SSL_VERIFY = beast::lexicalCastThrow<bool>(strTemp);
448 
449  if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_))
450  {
451  if (boost::iequals(strTemp, "all"))
452  RELAY_UNTRUSTED_VALIDATIONS = true;
453  else if (boost::iequals(strTemp, "trusted"))
454  RELAY_UNTRUSTED_VALIDATIONS = false;
455  else
456  Throw<std::runtime_error>(
457  "Invalid value specified in [" SECTION_RELAY_VALIDATIONS
458  "] section");
459  }
460 
461  if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_))
462  {
463  if (boost::iequals(strTemp, "all"))
464  RELAY_UNTRUSTED_PROPOSALS = true;
465  else if (boost::iequals(strTemp, "trusted"))
466  RELAY_UNTRUSTED_PROPOSALS = false;
467  else
468  Throw<std::runtime_error>(
469  "Invalid value specified in [" SECTION_RELAY_PROPOSALS
470  "] section");
471  }
472 
473  if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN))
474  Throw<std::runtime_error>("Cannot have both [" SECTION_VALIDATION_SEED
475  "] and [" SECTION_VALIDATOR_TOKEN
476  "] config sections");
477 
478  if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
479  NETWORK_QUORUM = beast::lexicalCastThrow<std::size_t>(strTemp);
480 
481  if (getSingleSection(secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
482  FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
483 
484  if (getSingleSection(secConfig, SECTION_FEE_OWNER_RESERVE, strTemp, j_))
485  FEE_OWNER_RESERVE = beast::lexicalCastThrow<std::uint64_t>(strTemp);
486 
487  if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_))
488  FEE_DEFAULT = beast::lexicalCastThrow<std::uint64_t>(strTemp);
489 
490  if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_))
491  {
492  if (boost::iequals(strTemp, "full"))
493  LEDGER_HISTORY =
494  std::numeric_limits<decltype(LEDGER_HISTORY)>::max();
495  else if (boost::iequals(strTemp, "none"))
496  LEDGER_HISTORY = 0;
497  else
498  LEDGER_HISTORY = beast::lexicalCastThrow<std::uint32_t>(strTemp);
499  }
500 
501  if (getSingleSection(secConfig, SECTION_FETCH_DEPTH, strTemp, j_))
502  {
503  if (boost::iequals(strTemp, "none"))
504  FETCH_DEPTH = 0;
505  else if (boost::iequals(strTemp, "full"))
506  FETCH_DEPTH = std::numeric_limits<decltype(FETCH_DEPTH)>::max();
507  else
508  FETCH_DEPTH = beast::lexicalCastThrow<std::uint32_t>(strTemp);
509 
510  if (FETCH_DEPTH < 10)
511  FETCH_DEPTH = 10;
512  }
513 
514  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_))
515  PATH_SEARCH_OLD = beast::lexicalCastThrow<int>(strTemp);
516  if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_))
517  PATH_SEARCH = beast::lexicalCastThrow<int>(strTemp);
518  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_))
519  PATH_SEARCH_FAST = beast::lexicalCastThrow<int>(strTemp);
520  if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
521  PATH_SEARCH_MAX = beast::lexicalCastThrow<int>(strTemp);
522 
523  if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
524  DEBUG_LOGFILE = strTemp;
525 
526  if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_))
527  WORKERS = beast::lexicalCastThrow<std::size_t>(strTemp);
528 
529  if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
530  COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
531 
532  if (getSingleSection(secConfig, SECTION_LEDGER_REPLAY, strTemp, j_))
533  LEDGER_REPLAY = beast::lexicalCastThrow<bool>(strTemp);
534 
535  if (exists(SECTION_REDUCE_RELAY))
536  {
537  auto sec = section(SECTION_REDUCE_RELAY);
538  VP_REDUCE_RELAY_ENABLE = sec.value_or("vp_enable", false);
539  VP_REDUCE_RELAY_SQUELCH = sec.value_or("vp_squelch", false);
540  }
541 
542  if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))
543  {
544  MAX_TRANSACTIONS = std::clamp(
545  beast::lexicalCastThrow<int>(strTemp),
546  MIN_JOB_QUEUE_TX,
547  MAX_JOB_QUEUE_TX);
548  }
549 
550  if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_))
551  {
552  if (!isProperlyFormedTomlDomain(strTemp))
553  {
554  Throw<std::runtime_error>(
555  "Invalid " SECTION_SERVER_DOMAIN
556  ": the domain name does not appear to meet the requirements.");
557  }
558 
559  SERVER_DOMAIN = strTemp;
560  }
561 
562  if (exists(SECTION_OVERLAY))
563  {
564  auto const sec = section(SECTION_OVERLAY);
565 
566  using namespace std::chrono;
567 
568  try
569  {
570  if (auto val = sec.get<std::string>("max_unknown_time"))
571  MAX_UNKNOWN_TIME =
572  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
573  }
574  catch (...)
575  {
576  Throw<std::runtime_error>(
577  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
578  ": must be of the form '<number>' representing seconds.");
579  }
580 
581  if (MAX_UNKNOWN_TIME < seconds{300} || MAX_UNKNOWN_TIME > seconds{1800})
582  Throw<std::runtime_error>(
583  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
584  ": the time must be between 300 and 1800 seconds, inclusive.");
585 
586  try
587  {
588  if (auto val = sec.get<std::string>("max_diverged_time"))
589  MAX_DIVERGED_TIME =
590  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
591  }
592  catch (...)
593  {
594  Throw<std::runtime_error>(
595  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
596  ": must be of the form '<number>' representing seconds.");
597  }
598 
599  if (MAX_DIVERGED_TIME < seconds{60} || MAX_DIVERGED_TIME > seconds{900})
600  {
601  Throw<std::runtime_error>(
602  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
603  ": the time must be between 60 and 900 seconds, inclusive.");
604  }
605  }
606 
607  if (getSingleSection(
608  secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
609  {
610  using namespace std::chrono;
611  boost::regex const re(
612  "^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
613  boost::smatch match;
614  if (!boost::regex_match(strTemp, match, re))
615  Throw<std::runtime_error>(
616  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
617  ", must be: [0-9]+ [minutes|hours|days|weeks]");
618 
620  beast::lexicalCastThrow<std::uint32_t>(match[1].str());
621 
622  if (boost::iequals(match[2], "minutes"))
623  AMENDMENT_MAJORITY_TIME = minutes(duration);
624  else if (boost::iequals(match[2], "hours"))
625  AMENDMENT_MAJORITY_TIME = hours(duration);
626  else if (boost::iequals(match[2], "days"))
627  AMENDMENT_MAJORITY_TIME = days(duration);
628  else if (boost::iequals(match[2], "weeks"))
629  AMENDMENT_MAJORITY_TIME = weeks(duration);
630 
631  if (AMENDMENT_MAJORITY_TIME < minutes(15))
632  Throw<std::runtime_error>(
633  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
634  ", the minimum amount of time an amendment must hold a "
635  "majority is 15 minutes");
636  }
637 
638  // Do not load trusted validator configuration for standalone mode
639  if (!RUN_STANDALONE)
640  {
641  // If a file was explicitly specified, then throw if the
642  // path is malformed or if the file does not exist or is
643  // not a file.
644  // If the specified file is not an absolute path, then look
645  // for it in the same directory as the config file.
646  // If no path was specified, then look for validators.txt
647  // in the same directory as the config file, but don't complain
648  // if we can't find it.
649  boost::filesystem::path validatorsFile;
650 
651  if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
652  {
653  validatorsFile = strTemp;
654 
655  if (validatorsFile.empty())
656  Throw<std::runtime_error>(
657  "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
658 
659  if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
660  validatorsFile = CONFIG_DIR / validatorsFile;
661 
662  if (!boost::filesystem::exists(validatorsFile))
663  Throw<std::runtime_error>(
664  "The file specified in [" SECTION_VALIDATORS_FILE
665  "] "
666  "does not exist: " +
667  validatorsFile.string());
668 
669  else if (
670  !boost::filesystem::is_regular_file(validatorsFile) &&
671  !boost::filesystem::is_symlink(validatorsFile))
672  Throw<std::runtime_error>(
673  "Invalid file specified in [" SECTION_VALIDATORS_FILE
674  "]: " +
675  validatorsFile.string());
676  }
677  else if (!CONFIG_DIR.empty())
678  {
679  validatorsFile = CONFIG_DIR / validatorsFileName;
680 
681  if (!validatorsFile.empty())
682  {
683  if (!boost::filesystem::exists(validatorsFile))
684  validatorsFile.clear();
685  else if (
686  !boost::filesystem::is_regular_file(validatorsFile) &&
687  !boost::filesystem::is_symlink(validatorsFile))
688  validatorsFile.clear();
689  }
690  }
691 
692  if (!validatorsFile.empty() &&
693  boost::filesystem::exists(validatorsFile) &&
694  (boost::filesystem::is_regular_file(validatorsFile) ||
695  boost::filesystem::is_symlink(validatorsFile)))
696  {
697  boost::system::error_code ec;
698  auto const data = getFileContents(ec, validatorsFile);
699  if (ec)
700  {
701  Throw<std::runtime_error>(
702  "Failed to read '" + validatorsFile.string() + "'." +
703  std::to_string(ec.value()) + ": " + ec.message());
704  }
705 
706  auto iniFile = parseIniFile(data, true);
707 
708  auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS);
709 
710  if (entries)
711  section(SECTION_VALIDATORS).append(*entries);
712 
713  auto valKeyEntries =
714  getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS);
715 
716  if (valKeyEntries)
717  section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries);
718 
719  auto valSiteEntries =
720  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES);
721 
722  if (valSiteEntries)
723  section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries);
724 
725  auto valListKeys =
726  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS);
727 
728  if (valListKeys)
729  section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
730 
731  if (!entries && !valKeyEntries && !valListKeys)
732  Throw<std::runtime_error>(
733  "The file specified in [" SECTION_VALIDATORS_FILE
734  "] "
735  "does not contain a [" SECTION_VALIDATORS
736  "], "
737  "[" SECTION_VALIDATOR_KEYS
738  "] or "
739  "[" SECTION_VALIDATOR_LIST_KEYS
740  "]"
741  " section: " +
742  validatorsFile.string());
743  }
744 
745  // Consolidate [validator_keys] and [validators]
746  section(SECTION_VALIDATORS)
747  .append(section(SECTION_VALIDATOR_KEYS).lines());
748 
749  if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
750  section(SECTION_VALIDATOR_LIST_KEYS).lines().empty())
751  {
752  Throw<std::runtime_error>(
753  "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
754  "] config section is missing");
755  }
756  }
757 
758  {
759  auto const part = section("features");
760  for (auto const& s : part.values())
761  {
762  if (auto const f = getRegisteredFeature(s))
763  features.insert(*f);
764  else
765  Throw<std::runtime_error>(
766  "Unknown feature: " + s + " in config file.");
767  }
768  }
769 
770  // This doesn't properly belong here, but check to make sure that the
771  // value specified for network_quorum is achievable:
772  {
773  auto pm = PEERS_MAX;
774 
775  // FIXME this apparently magic value is actually defined as a constant
776  // elsewhere (see defaultMaxPeers) but we handle this check here.
777  if (pm == 0)
778  pm = 21;
779 
780  if (NETWORK_QUORUM > pm)
781  {
782  Throw<std::runtime_error>(
783  "The minimum number of required peers (network_quorum) exceeds "
784  "the maximum number of allowed peers (peers_max)");
785  }
786  }
787 }
788 
789 boost::filesystem::path
790 Config::getDebugLogFile() const
791 {
792  auto log_file = DEBUG_LOGFILE;
793 
794  if (!log_file.empty() && !log_file.is_absolute())
795  {
796  // Unless an absolute path for the log file is specified, the
797  // path is relative to the config file directory.
798  log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
799  }
800 
801  if (!log_file.empty())
802  {
803  auto log_dir = log_file.parent_path();
804 
805  if (!boost::filesystem::is_directory(log_dir))
806  {
807  boost::system::error_code ec;
808  boost::filesystem::create_directories(log_dir, ec);
809 
810  // If we fail, we warn but continue so that the calling code can
811  // decide how to handle this situation.
812  if (ec)
813  {
814  std::cerr << "Unable to create log file path " << log_dir
815  << ": " << ec.message() << '\n';
816  }
817  }
818  }
819 
820  return log_file;
821 }
822 
823 int
824 Config::getValueFor(SizedItem item, std::optional<std::size_t> node) const
825 {
826  auto const index = static_cast<std::underlying_type_t<SizedItem>>(item);
827  assert(index < sizedItems.size());
828  assert(!node || *node <= 4);
829  return sizedItems.at(index).second.at(node.value_or(NODE_SIZE));
830 }
831 
832 } // namespace ripple
ripple::SizedItem::openFinalLimit
@ openFinalLimit
ripple::Section
Holds a collection of configuration values.
Definition: BasicConfig.h:43
ripple::sizedItems
constexpr std::array< std::pair< SizedItem, std::array< int, 5 > >, 11 > sizedItems
Definition: Config.cpp:45
ripple::getFileContents
std::string getFileContents(boost::system::error_code &ec, boost::filesystem::path const &sourcePath, std::optional< std::size_t > maxSize=std::nullopt)
Definition: FileUtilities.cpp:25
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::optional::value_or
T value_or(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
ripple::get_if_exists
bool get_if_exists(Section const &section, std::string const &name, T &v)
Definition: BasicConfig.h:347
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::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< std::size_t >
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)
ripple::getRegisteredFeature
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:145
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
std::chrono