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 (exists(SECTION_REDUCE_RELAY))
533  {
534  auto sec = section(SECTION_REDUCE_RELAY);
535  VP_REDUCE_RELAY_ENABLE = sec.value_or("vp_enable", false);
536  VP_REDUCE_RELAY_SQUELCH = sec.value_or("vp_squelch", false);
537  }
538 
539  if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))
540  {
541  MAX_TRANSACTIONS = std::clamp(
542  beast::lexicalCastThrow<int>(strTemp),
543  MIN_JOB_QUEUE_TX,
544  MAX_JOB_QUEUE_TX);
545  }
546 
547  if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_))
548  {
549  if (!isProperlyFormedTomlDomain(strTemp))
550  {
551  Throw<std::runtime_error>(
552  "Invalid " SECTION_SERVER_DOMAIN
553  ": the domain name does not appear to meet the requirements.");
554  }
555 
556  SERVER_DOMAIN = strTemp;
557  }
558 
559  if (exists(SECTION_OVERLAY))
560  {
561  auto const sec = section(SECTION_OVERLAY);
562 
563  using namespace std::chrono;
564 
565  try
566  {
567  if (auto val = sec.get<std::string>("max_unknown_time"))
568  MAX_UNKNOWN_TIME =
569  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
570  }
571  catch (...)
572  {
573  Throw<std::runtime_error>(
574  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
575  ": must be of the form '<number>' representing seconds.");
576  }
577 
578  if (MAX_UNKNOWN_TIME < seconds{300} || MAX_UNKNOWN_TIME > seconds{1800})
579  Throw<std::runtime_error>(
580  "Invalid value 'max_unknown_time' in " SECTION_OVERLAY
581  ": the time must be between 300 and 1800 seconds, inclusive.");
582 
583  try
584  {
585  if (auto val = sec.get<std::string>("max_diverged_time"))
586  MAX_DIVERGED_TIME =
587  seconds{beast::lexicalCastThrow<std::uint32_t>(*val)};
588  }
589  catch (...)
590  {
591  Throw<std::runtime_error>(
592  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
593  ": must be of the form '<number>' representing seconds.");
594  }
595 
596  if (MAX_DIVERGED_TIME < seconds{60} || MAX_DIVERGED_TIME > seconds{900})
597  {
598  Throw<std::runtime_error>(
599  "Invalid value 'max_diverged_time' in " SECTION_OVERLAY
600  ": the time must be between 60 and 900 seconds, inclusive.");
601  }
602  }
603 
604  if (getSingleSection(
605  secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
606  {
607  using namespace std::chrono;
608  boost::regex const re(
609  "^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
610  boost::smatch match;
611  if (!boost::regex_match(strTemp, match, re))
612  Throw<std::runtime_error>(
613  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
614  ", must be: [0-9]+ [minutes|hours|days|weeks]");
615 
617  beast::lexicalCastThrow<std::uint32_t>(match[1].str());
618 
619  if (boost::iequals(match[2], "minutes"))
620  AMENDMENT_MAJORITY_TIME = minutes(duration);
621  else if (boost::iequals(match[2], "hours"))
622  AMENDMENT_MAJORITY_TIME = hours(duration);
623  else if (boost::iequals(match[2], "days"))
624  AMENDMENT_MAJORITY_TIME = days(duration);
625  else if (boost::iequals(match[2], "weeks"))
626  AMENDMENT_MAJORITY_TIME = weeks(duration);
627 
628  if (AMENDMENT_MAJORITY_TIME < minutes(15))
629  Throw<std::runtime_error>(
630  "Invalid " SECTION_AMENDMENT_MAJORITY_TIME
631  ", the minimum amount of time an amendment must hold a "
632  "majority is 15 minutes");
633  }
634 
635  // Do not load trusted validator configuration for standalone mode
636  if (!RUN_STANDALONE)
637  {
638  // If a file was explicitly specified, then throw if the
639  // path is malformed or if the file does not exist or is
640  // not a file.
641  // If the specified file is not an absolute path, then look
642  // for it in the same directory as the config file.
643  // If no path was specified, then look for validators.txt
644  // in the same directory as the config file, but don't complain
645  // if we can't find it.
646  boost::filesystem::path validatorsFile;
647 
648  if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
649  {
650  validatorsFile = strTemp;
651 
652  if (validatorsFile.empty())
653  Throw<std::runtime_error>(
654  "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
655 
656  if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
657  validatorsFile = CONFIG_DIR / validatorsFile;
658 
659  if (!boost::filesystem::exists(validatorsFile))
660  Throw<std::runtime_error>(
661  "The file specified in [" SECTION_VALIDATORS_FILE
662  "] "
663  "does not exist: " +
664  validatorsFile.string());
665 
666  else if (
667  !boost::filesystem::is_regular_file(validatorsFile) &&
668  !boost::filesystem::is_symlink(validatorsFile))
669  Throw<std::runtime_error>(
670  "Invalid file specified in [" SECTION_VALIDATORS_FILE
671  "]: " +
672  validatorsFile.string());
673  }
674  else if (!CONFIG_DIR.empty())
675  {
676  validatorsFile = CONFIG_DIR / validatorsFileName;
677 
678  if (!validatorsFile.empty())
679  {
680  if (!boost::filesystem::exists(validatorsFile))
681  validatorsFile.clear();
682  else if (
683  !boost::filesystem::is_regular_file(validatorsFile) &&
684  !boost::filesystem::is_symlink(validatorsFile))
685  validatorsFile.clear();
686  }
687  }
688 
689  if (!validatorsFile.empty() &&
690  boost::filesystem::exists(validatorsFile) &&
691  (boost::filesystem::is_regular_file(validatorsFile) ||
692  boost::filesystem::is_symlink(validatorsFile)))
693  {
694  boost::system::error_code ec;
695  auto const data = getFileContents(ec, validatorsFile);
696  if (ec)
697  {
698  Throw<std::runtime_error>(
699  "Failed to read '" + validatorsFile.string() + "'." +
700  std::to_string(ec.value()) + ": " + ec.message());
701  }
702 
703  auto iniFile = parseIniFile(data, true);
704 
705  auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS);
706 
707  if (entries)
708  section(SECTION_VALIDATORS).append(*entries);
709 
710  auto valKeyEntries =
711  getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS);
712 
713  if (valKeyEntries)
714  section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries);
715 
716  auto valSiteEntries =
717  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES);
718 
719  if (valSiteEntries)
720  section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries);
721 
722  auto valListKeys =
723  getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS);
724 
725  if (valListKeys)
726  section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
727 
728  if (!entries && !valKeyEntries && !valListKeys)
729  Throw<std::runtime_error>(
730  "The file specified in [" SECTION_VALIDATORS_FILE
731  "] "
732  "does not contain a [" SECTION_VALIDATORS
733  "], "
734  "[" SECTION_VALIDATOR_KEYS
735  "] or "
736  "[" SECTION_VALIDATOR_LIST_KEYS
737  "]"
738  " section: " +
739  validatorsFile.string());
740  }
741 
742  // Consolidate [validator_keys] and [validators]
743  section(SECTION_VALIDATORS)
744  .append(section(SECTION_VALIDATOR_KEYS).lines());
745 
746  if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
747  section(SECTION_VALIDATOR_LIST_KEYS).lines().empty())
748  {
749  Throw<std::runtime_error>(
750  "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
751  "] config section is missing");
752  }
753  }
754 
755  {
756  auto const part = section("features");
757  for (auto const& s : part.values())
758  {
759  if (auto const f = getRegisteredFeature(s))
760  features.insert(*f);
761  else
762  Throw<std::runtime_error>(
763  "Unknown feature: " + s + " in config file.");
764  }
765  }
766 
767  // This doesn't properly belong here, but check to make sure that the
768  // value specified for network_quorum is achievable:
769  {
770  auto pm = PEERS_MAX;
771 
772  // FIXME this apparently magic value is actually defined as a constant
773  // elsewhere (see defaultMaxPeers) but we handle this check here.
774  if (pm == 0)
775  pm = 21;
776 
777  if (NETWORK_QUORUM > pm)
778  {
779  Throw<std::runtime_error>(
780  "The minimum number of required peers (network_quorum) exceeds "
781  "the maximum number of allowed peers (peers_max)");
782  }
783  }
784 }
785 
786 boost::filesystem::path
787 Config::getDebugLogFile() const
788 {
789  auto log_file = DEBUG_LOGFILE;
790 
791  if (!log_file.empty() && !log_file.is_absolute())
792  {
793  // Unless an absolute path for the log file is specified, the
794  // path is relative to the config file directory.
795  log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
796  }
797 
798  if (!log_file.empty())
799  {
800  auto log_dir = log_file.parent_path();
801 
802  if (!boost::filesystem::is_directory(log_dir))
803  {
804  boost::system::error_code ec;
805  boost::filesystem::create_directories(log_dir, ec);
806 
807  // If we fail, we warn but continue so that the calling code can
808  // decide how to handle this situation.
809  if (ec)
810  {
811  std::cerr << "Unable to create log file path " << log_dir
812  << ": " << ec.message() << '\n';
813  }
814  }
815  }
816 
817  return log_file;
818 }
819 
820 int
821 Config::getValueFor(SizedItem item, boost::optional<std::size_t> node) const
822 {
823  auto const index = static_cast<std::underlying_type_t<SizedItem>>(item);
824  assert(index < sizedItems.size());
825  assert(!node || *node <= 4);
826  return sizedItems.at(index).second.at(node.value_or(NODE_SIZE));
827 }
828 
829 } // 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
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
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::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