diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -305,6 +305,7 @@ uint256.cpp util/bytevectorhash.cpp util/moneystr.cpp + util/settings.cpp util/strencodings.cpp util/system.cpp util/threadnames.cpp diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -233,6 +233,7 @@ util/macros.h \ util/moneystr.h \ util/system.h \ + util/settings.h \ util/threadnames.h \ util/time.h \ validation.h \ @@ -519,6 +520,7 @@ uint256.h \ util/system.cpp \ util/moneystr.cpp \ + util/settings.cpp \ util/strencodings.cpp \ util/threadnames.cpp \ util/time.cpp \ diff --git a/src/util/settings.h b/src/util/settings.h new file mode 100644 --- /dev/null +++ b/src/util/settings.h @@ -0,0 +1,98 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_SETTINGS_H +#define BITCOIN_UTIL_SETTINGS_H + +#include +#include +#include + +class UniValue; + +namespace util { + +//! Settings value type (string/integer/boolean/null variant). +//! +//! @note UniValue is used here for convenience and because it can be easily +//! serialized in a readable format. But any other variant type that can +//! be assigned strings, int64_t, and bool values and has get_str(), +//! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and +//! isNull() methods can be substituted if there's a need to move away +//! from UniValue. (An implementation with boost::variant was posted at +//! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) +using SettingsValue = UniValue; + +//! Stored bitcoin settings. This struct combines settings from the command line +//! and a read-only configuration file. +struct Settings { + //! Map of setting name to forced setting value. + std::map forced_settings; + //! Map of setting name to list of command line values. + std::map> command_line_options; + //! Map of config section name and setting name to list of config file + //! values. + std::map>> + ro_config; +}; + +//! Get settings value from combined sources: forced settings, command line +//! arguments and the read-only config file. +//! +//! @param ignore_default_section_config - ignore values in the default section +//! of the config file (part before any +//! [section] keywords) +//! @param get_chain_name - enable special backwards compatible behavior +//! for GetChainName +SettingsValue GetSetting(const Settings &settings, const std::string §ion, + const std::string &name, + bool ignore_default_section_config, + bool get_chain_name); + +//! Get combined setting value similar to GetSetting(), except if setting was +//! specified multiple times, return a list of all the values specified. +std::vector GetSettingsList(const Settings &settings, + const std::string §ion, + const std::string &name, + bool ignore_default_section_config); + +//! Return true if a setting is set in the default config file section, and not +//! overridden by a higher priority command-line or network section value. +//! +//! This is used to provide user warnings about values that might be getting +//! ignored unintentionally. +bool OnlyHasDefaultSectionSetting(const Settings &settings, + const std::string §ion, + const std::string &name); + +//! Accessor for list of settings that skips negated values when iterated over. +//! The last boolean `false` value in the list and all earlier values are +//! considered negated. +struct SettingsSpan { + explicit SettingsSpan() = default; + explicit SettingsSpan(const SettingsValue &value) noexcept + : SettingsSpan(&value, 1) {} + explicit SettingsSpan(const SettingsValue *dataIn, size_t sizeIn) noexcept + : data(dataIn), size(sizeIn) {} + explicit SettingsSpan(const std::vector &vec) noexcept; + const SettingsValue *begin() const; // +auto FindKey(Map &&map, Key &&key) -> decltype(&map.at(key)) { + auto it = map.find(key); + return it == map.end() ? nullptr : &it->second; +} + +} // namespace util + +#endif // BITCOIN_UTIL_SETTINGS_H diff --git a/src/util/settings.cpp b/src/util/settings.cpp new file mode 100644 --- /dev/null +++ b/src/util/settings.cpp @@ -0,0 +1,210 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +namespace util { +namespace { + + enum class Source { + FORCED, + COMMAND_LINE, + CONFIG_FILE_NETWORK_SECTION, + CONFIG_FILE_DEFAULT_SECTION + }; + + //! Merge settings from multiple sources in precedence order: + //! Forced config > command line > config file network-specific section > + //! config file default section + //! + //! This function is provided with a callback function fn that contains + //! specific logic for how to merge the sources. + template + static void MergeSettings(const Settings &settings, + const std::string §ion, + const std::string &name, Fn &&fn) { + // Merge in the forced settings + if (auto *value = FindKey(settings.forced_settings, name)) { + fn(SettingsSpan(*value), Source::FORCED); + } + // Merge in the command-line options + if (auto *values = FindKey(settings.command_line_options, name)) { + fn(SettingsSpan(*values), Source::COMMAND_LINE); + } + // Merge in the network-specific section of the config file + if (!section.empty()) { + if (auto *map = FindKey(settings.ro_config, section)) { + if (auto *values = FindKey(*map, name)) { + fn(SettingsSpan(*values), + Source::CONFIG_FILE_NETWORK_SECTION); + } + } + } + // Merge in the default section of the config file + if (auto *map = FindKey(settings.ro_config, "")) { + if (auto *values = FindKey(*map, name)) { + fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); + } + } + } +} // namespace + +SettingsValue GetSetting(const Settings &settings, const std::string §ion, + const std::string &name, + bool ignore_default_section_config, + bool get_chain_name) { + SettingsValue result; + MergeSettings( + settings, section, name, [&](SettingsSpan span, Source source) { + // Weird behavior preserved for backwards compatibility: Apply + // negated setting even if non-negated setting would be ignored. A + // negated value in the default section is applied to network + // specific options, even though normal non-negated values there + // would be ignored. + const bool never_ignore_negated_setting = span.last_negated(); + + // Weird behavior preserved for backwards compatibility: Take first + // assigned value instead of last. In general, later settings take + // precedence over early settings, but for backwards compatibility + // in the config file the precedence is reversed for all settings + // except chain name settings. + const bool reverse_precedence = + (source == Source::CONFIG_FILE_NETWORK_SECTION || + source == Source::CONFIG_FILE_DEFAULT_SECTION) && + !get_chain_name; + + // Weird behavior preserved for backwards compatibility: Negated + // -regtest and -testnet arguments which you would expect to + // override values set in the configuration file are currently + // accepted but silently ignored. It would be better to apply these + // just like other negated values, or at least warn they are + // ignored. + const bool skip_negated_command_line = get_chain_name; + + // Ignore settings in default config section if requested. + if (ignore_default_section_config && + source == Source::CONFIG_FILE_DEFAULT_SECTION && + !never_ignore_negated_setting) { + return; + } + + // Skip negated command line settings. + if (skip_negated_command_line && span.last_negated()) { + return; + } + + // Stick with highest priority value, keeping result if already set. + if (!result.isNull()) { + return; + } + + if (!span.empty()) { + result = reverse_precedence ? span.begin()[0] : span.end()[-1]; + } else if (span.last_negated()) { + result = false; + } + }); + return result; +} + +std::vector GetSettingsList(const Settings &settings, + const std::string §ion, + const std::string &name, + bool ignore_default_section_config) { + std::vector result; + bool result_complete = false; + bool prev_negated_empty = false; + MergeSettings( + settings, section, name, [&](SettingsSpan span, Source source) { + // Weird behavior preserved for backwards compatibility: Apply + // config file settings even if negated on command line. Negating a + // setting on command line will ignore earlier settings on the + // command line and ignore settings in the config file, unless the + // negated command line value is followed by non-negated value, in + // which case config file settings will be brought back from the + // dead (but earlier command line settings will still be ignored). + const bool add_zombie_config_values = + (source == Source::CONFIG_FILE_NETWORK_SECTION || + source == Source::CONFIG_FILE_DEFAULT_SECTION) && + !prev_negated_empty; + + // Ignore settings in default config section if requested. + if (ignore_default_section_config && + source == Source::CONFIG_FILE_DEFAULT_SECTION) { + return; + } + + // Add new settings to the result if isn't already complete, or if + // the values are zombies. + if (!result_complete || add_zombie_config_values) { + for (const auto &value : span) { + if (value.isArray()) { + result.insert(result.end(), value.getValues().begin(), + value.getValues().end()); + } else { + result.push_back(value); + } + } + } + + // If a setting was negated, or if a setting was forced, set + // result_complete to true to ignore any later lower priority + // settings. + result_complete |= span.negated() > 0 || source == Source::FORCED; + + // Update the negated and empty state used for the zombie values + // check. + prev_negated_empty |= span.last_negated() && result.empty(); + }); + return result; +} + +bool OnlyHasDefaultSectionSetting(const Settings &settings, + const std::string §ion, + const std::string &name) { + bool has_default_section_setting = false; + bool has_other_setting = false; + MergeSettings( + settings, section, name, [&](SettingsSpan span, Source source) { + if (span.empty()) { + return; + } else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) { + has_default_section_setting = true; + } else { + has_other_setting = true; + } + }); + // If a value is set in the default section and not explicitly overwritten + // by the user on the command line or in a different section, then we want + // to enable warnings about the value being ignored. + return has_default_section_setting && !has_other_setting; +} + +SettingsSpan::SettingsSpan(const std::vector &vec) noexcept + : SettingsSpan(vec.data(), vec.size()) {} +const SettingsValue *SettingsSpan::begin() const { + return data + negated(); +} +const SettingsValue *SettingsSpan::end() const { + return data + size; +} +bool SettingsSpan::empty() const { + return size == 0 || last_negated(); +} +bool SettingsSpan::last_negated() const { + return size > 0 && data[size - 1].isFalse(); +} +size_t SettingsSpan::negated() const { + for (size_t i = size; i > 0; --i) { + if (data[i - 1].isFalse()) { + // Return number of negated values (position of last false value) + return i; + } + } + return 0; +} + +} // namespace util