Skip to content

Commit 9c69cfe

Browse files
committed
Add <datadir>/settings.json persistent settings storage.
Persistent settings are used in followup PRs #15936 to unify gui settings between bitcoin-qt and bitcoind, and #15937 to add a load_on_startup flag to the loadwallet RPC and maintain a dynamic list of wallets that should be loaded on startup that also can be shared between bitcoind and bitcoin-qt.
1 parent eb682c5 commit 9c69cfe

File tree

13 files changed

+255
-6
lines changed

13 files changed

+255
-6
lines changed

doc/files.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ Subdirectory | File(s) | Description
5050
`indexes/blockfilter/basic/` | `fltrNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Blockfilter index filters for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
5151
`wallets/` | | [Contains wallets](#multi-wallet-environment); can be specified by `-walletdir` option; if `wallets/` subdirectory does not exist, a wallet resides in the data directory
5252
`./` | `banlist.dat` | Stores the IPs/subnets of banned nodes
53-
`./` | `bitcoin.conf` | Contains [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`; can be specified by `-conf` option
53+
`./` | `bitcoin.conf` | User-defined [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`. File is not written to by the software and must be created manually. Path can be specified by `-conf` option
5454
`./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option
5555
`./` | `debug.log` | Contains debug information and general logging generated by `bitcoind` or `bitcoin-qt`; can be specified by `-debuglogfile` option
5656
`./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees and priorities required for confirmation
5757
`./` | `guisettings.ini.bak` | Backup of former [GUI settings](#gui-settings) after `-resetguisettings` option is used
5858
`./` | `mempool.dat` | Dump of the mempool's transactions
5959
`./` | `onion_private_key` | Cached Tor hidden service private key for `-listenonion` option
6060
`./` | `peers.dat` | Peer IP address database (custom format)
61+
`./` | `settings.json` | Read-write settings set through GUI or RPC interfaces, augmenting manual settings from [bitcoin.conf](bitcoin-conf.md). File is created automatically if read-write settings storage is not disabled with `-nosettings` option. Path can be specified with `-settings` option
6162
`./` | `.cookie` | Session RPC authentication cookie; if used, created at start and deleted on shutdown; can be specified by `-rpccookiefile` option
6263
`./` | `.lock` | Data directory lock file
6364

src/bitcoind.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ static bool AppInit(int argc, char* argv[])
101101
}
102102
}
103103

104+
if (!gArgs.InitSettings(error)) {
105+
InitError(Untranslated(error));
106+
return false;
107+
}
108+
104109
// -server defaults to true for bitcoind but not for the GUI so do this here
105110
gArgs.SoftSetBoolArg("-server", true);
106111
// Set this early so that parameter interactions go to console

src/init.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ void SetupServerArgs(NodeContext& node)
397397
#endif
398398
gArgs.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
399399
gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
400-
gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
400+
gArgs.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
401401
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
402402
gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
403403
gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -418,6 +418,7 @@ void SetupServerArgs(NodeContext& node)
418418
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
419419
gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
420420
gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
421+
gArgs.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
421422
#ifndef WIN32
422423
gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
423424
#else

src/interfaces/node.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class NodeImpl : public Node
6666
bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); }
6767
bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); }
6868
void selectParams(const std::string& network) override { SelectParams(network); }
69+
bool initSettings(std::string& error) override { return gArgs.InitSettings(error); }
6970
uint64_t getAssumedBlockchainSize() override { return Params().AssumedBlockchainSize(); }
7071
uint64_t getAssumedChainStateSize() override { return Params().AssumedChainStateSize(); }
7172
std::string getNetwork() override { return Params().NetworkIDString(); }

src/interfaces/node.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ class Node
6666
//! Choose network parameters.
6767
virtual void selectParams(const std::string& network) = 0;
6868

69+
//! Read and update <datadir>/settings.json file with saved settings. This
70+
//! needs to be called after selectParams() because the settings file
71+
//! location is network-specific.
72+
virtual bool initSettings(std::string& error) = 0;
73+
6974
//! Get the (assumed) blockchain size.
7075
virtual uint64_t getAssumedBlockchainSize() = 0;
7176

src/qt/bitcoin.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@ int GuiMain(int argc, char* argv[])
528528
// Parse URIs on command line -- this can affect Params()
529529
PaymentServer::ipcParseCommandLine(*node, argc, argv);
530530
#endif
531+
if (!node->initSettings(error)) {
532+
node->initError(Untranslated(error));
533+
QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error initializing settings: %1").arg(QString::fromStdString(error)));
534+
return EXIT_FAILURE;
535+
}
531536

532537
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().NetworkIDString()));
533538
assert(!networkStyle.isNull());

src/test/util_tests.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <key.h> // For CKey
1010
#include <optional.h>
1111
#include <sync.h>
12+
#include <test/util/logging.h>
1213
#include <test/util/setup_common.h>
1314
#include <test/util/str.h>
1415
#include <uint256.h>
@@ -1128,6 +1129,28 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
11281129
BOOST_CHECK_EQUAL(out_sha_hex, "f0b3a3c29869edc765d579c928f7f1690a71fbb673b49ccf39cbc4de18156a0d");
11291130
}
11301131

1132+
BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
1133+
{
1134+
// Test writing setting.
1135+
TestArgsManager args1;
1136+
args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; });
1137+
args1.WriteSettingsFile();
1138+
1139+
// Test reading setting.
1140+
TestArgsManager args2;
1141+
args2.ReadSettingsFile();
1142+
args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); });
1143+
1144+
// Test error logging, and remove previously written setting.
1145+
{
1146+
ASSERT_DEBUG_LOG("Failed renaming settings file");
1147+
fs::remove(GetDataDir() / "settings.json");
1148+
fs::create_directory(GetDataDir() / "settings.json");
1149+
args2.WriteSettingsFile();
1150+
fs::remove(GetDataDir() / "settings.json");
1151+
}
1152+
}
1153+
11311154
BOOST_AUTO_TEST_CASE(util_FormatMoney)
11321155
{
11331156
BOOST_CHECK_EQUAL(FormatMoney(0), "0.00");

src/util/settings.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ namespace {
1313
enum class Source {
1414
FORCED,
1515
COMMAND_LINE,
16+
RW_SETTINGS,
1617
CONFIG_FILE_NETWORK_SECTION,
1718
CONFIG_FILE_DEFAULT_SECTION
1819
};
1920

2021
//! Merge settings from multiple sources in precedence order:
21-
//! Forced config > command line > config file network-specific section > config file default section
22+
//! Forced config > command line > read-write settings file > config file network-specific section > config file default section
2223
//!
2324
//! This function is provided with a callback function fn that contains
2425
//! specific logic for how to merge the sources.
@@ -33,6 +34,10 @@ static void MergeSettings(const Settings& settings, const std::string& section,
3334
if (auto* values = FindKey(settings.command_line_options, name)) {
3435
fn(SettingsSpan(*values), Source::COMMAND_LINE);
3536
}
37+
// Merge in the read-write settings
38+
if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
39+
fn(SettingsSpan(*value), Source::RW_SETTINGS);
40+
}
3641
// Merge in the network-specific section of the config file
3742
if (!section.empty()) {
3843
if (auto* map = FindKey(settings.ro_config, section)) {

src/util/settings.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ namespace util {
2626
//! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812)
2727
using SettingsValue = UniValue;
2828

29-
//! Stored bitcoin settings. This struct combines settings from the command line
30-
//! and a read-only configuration file.
29+
//! Stored settings. This struct combines settings from the command line, a
30+
//! read-only configuration file, and a read-write runtime settings file.
3131
struct Settings {
3232
//! Map of setting name to forced setting value.
3333
std::map<std::string, SettingsValue> forced_settings;
3434
//! Map of setting name to list of command line values.
3535
std::map<std::string, std::vector<SettingsValue>> command_line_options;
36+
//! Map of setting name to read-write file setting value.
37+
std::map<std::string, SettingsValue> rw_settings;
3638
//! Map of config section name and setting name to list of config file values.
3739
std::map<std::string, std::map<std::string, std::vector<SettingsValue>>> ro_config;
3840
};
@@ -48,7 +50,7 @@ bool WriteSettings(const fs::path& path,
4850
std::vector<std::string>& errors);
4951

5052
//! Get settings value from combined sources: forced settings, command line
51-
//! arguments and the read-only config file.
53+
//! arguments, runtime read-write settings, and the read-only config file.
5254
//!
5355
//! @param ignore_default_section_config - ignore values in the default section
5456
//! of the config file (part before any

src/util/system.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
const int64_t nStartupTime = GetTime();
7474

7575
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
76+
const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
7677

7778
ArgsManager gArgs;
7879

@@ -372,6 +373,84 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const
372373
return !GetSetting(strArg).isNull();
373374
}
374375

376+
bool ArgsManager::InitSettings(std::string& error)
377+
{
378+
if (!GetSettingsPath()) {
379+
return true; // Do nothing if settings file disabled.
380+
}
381+
382+
std::vector<std::string> errors;
383+
if (!ReadSettingsFile(&errors)) {
384+
error = strprintf("Failed loading settings file:\n- %s\n", Join(errors, "\n- "));
385+
return false;
386+
}
387+
if (!WriteSettingsFile(&errors)) {
388+
error = strprintf("Failed saving settings file:\n- %s\n", Join(errors, "\n- "));
389+
return false;
390+
}
391+
return true;
392+
}
393+
394+
bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const
395+
{
396+
if (IsArgNegated("-settings")) {
397+
return false;
398+
}
399+
if (filepath) {
400+
std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME);
401+
*filepath = fs::absolute(temp ? settings + ".tmp" : settings, GetDataDir(/* net_specific= */ true));
402+
}
403+
return true;
404+
}
405+
406+
static void SaveErrors(const std::vector<std::string> errors, std::vector<std::string>* error_out)
407+
{
408+
for (const auto& error : errors) {
409+
if (error_out) {
410+
error_out->emplace_back(error);
411+
} else {
412+
LogPrintf("%s\n", error);
413+
}
414+
}
415+
}
416+
417+
bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors)
418+
{
419+
fs::path path;
420+
if (!GetSettingsPath(&path, /* temp= */ false)) {
421+
return true; // Do nothing if settings file disabled.
422+
}
423+
424+
LOCK(cs_args);
425+
m_settings.rw_settings.clear();
426+
std::vector<std::string> read_errors;
427+
if (!util::ReadSettings(path, m_settings.rw_settings, read_errors)) {
428+
SaveErrors(read_errors, errors);
429+
return false;
430+
}
431+
return true;
432+
}
433+
434+
bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const
435+
{
436+
fs::path path, path_tmp;
437+
if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) {
438+
throw std::logic_error("Attempt to write settings file when dynamic settings are disabled.");
439+
}
440+
441+
LOCK(cs_args);
442+
std::vector<std::string> write_errors;
443+
if (!util::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) {
444+
SaveErrors(write_errors, errors);
445+
return false;
446+
}
447+
if (!RenameOver(path_tmp, path)) {
448+
SaveErrors({strprintf("Failed renaming settings file %s to %s\n", path_tmp.string(), path.string())}, errors);
449+
return false;
450+
}
451+
return true;
452+
}
453+
375454
bool ArgsManager::IsArgNegated(const std::string& strArg) const
376455
{
377456
return GetSetting(strArg).isFalse();
@@ -893,6 +972,9 @@ void ArgsManager::LogArgs() const
893972
for (const auto& section : m_settings.ro_config) {
894973
logArgsPrefix("Config file arg:", section.first, section.second);
895974
}
975+
for (const auto& setting : m_settings.rw_settings) {
976+
LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write());
977+
}
896978
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
897979
}
898980

0 commit comments

Comments
 (0)