Skip to content

Commit 70675c3

Browse files
committed
Add <datadir>/settings.json persistent settings storage.
Persistent settings are used in followup PRs bitcoin#15936 to unify gui settings between bitcoin-qt and bitcoind, and bitcoin#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 9f5e8ba commit 70675c3

File tree

9 files changed

+112
-4
lines changed

9 files changed

+112
-4
lines changed

doc/files.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Filename | Description
22
--------------------|----------------------------------------------------------------------------------------------------------------------------
33
banlist.dat | stores the IPs/Subnets of banned nodes
4-
bitcoin.conf | contains configuration settings for bitcoind or bitcoin-qt
4+
bitcoin.conf | user-defined settings for bitcoind and bitcoin-qt; file is read-only and must be created manually
55
bitcoind.pid | stores the process id of bitcoind while running
66
blocks/blk000??.dat | block data (custom, 128 MiB per file); since 0.8.0
77
blocks/rev000??.dat | block undo data (custom); since 0.8.0 (format changed since pre-0.8)
@@ -14,6 +14,7 @@ fee_estimates.dat | stores statistics used to estimate minimum transaction fee
1414
indexes/txindex/* | optional transaction index database (LevelDB); since 0.17.0
1515
mempool.dat | dump of the mempool's transactions; since 0.14.0
1616
peers.dat | peer IP address database (custom format); since 0.7.0
17+
setting.json | read-write settings set in GUI or RPC interfaces that augment manual settings from bitcoin.conf
1718
wallet.dat | personal wallet (BDB) with keys and transactions; moved to wallets/ directory on new installs since 0.16.0
1819
wallets/database/* | BDB database environment; used for wallets since 0.16.0
1920
wallets/db.log | wallet database log file; since 0.16.0

src/bitcoind.cpp

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

126+
if (!gArgs.ReadSettingsFile()) {
127+
fprintf(stderr, "Error reading settings file\n");
128+
return false;
129+
}
130+
126131
// -server defaults to true for bitcoind but not for the GUI so do this here
127132
gArgs.SoftSetBoolArg("-server", true);
128133
// Set this early so that parameter interactions go to console

src/interfaces/node.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class NodeImpl : public Node
6363
bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); }
6464
bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); }
6565
void selectParams(const std::string& network) override { SelectParams(network); }
66+
bool readSettingsFile() override { return gArgs.ReadSettingsFile(); }
6667
uint64_t getAssumedBlockchainSize() override { return Params().AssumedBlockchainSize(); }
6768
uint64_t getAssumedChainStateSize() override { return Params().AssumedChainStateSize(); }
6869
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
@@ -53,6 +53,11 @@ class Node
5353
//! Choose network parameters.
5454
virtual void selectParams(const std::string& network) = 0;
5555

56+
//! Load <datadir>/settings.json file with saved settings. This needs to be
57+
//! called after selectParams() because the settings file is
58+
//! network-specific.
59+
virtual bool readSettingsFile() = 0;
60+
5661
//! Get the (assumed) blockchain size.
5762
virtual uint64_t getAssumedBlockchainSize() = 0;
5863

src/qt/bitcoin.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,11 @@ int GuiMain(int argc, char* argv[])
527527
// Parse URIs on command line -- this can affect Params()
528528
PaymentServer::ipcParseCommandLine(*node, argc, argv);
529529
#endif
530+
if (!node->readSettingsFile()) {
531+
QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME),
532+
QObject::tr("Error: Cannot read settings file."));
533+
return EXIT_FAILURE;
534+
}
530535

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

src/util/settings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ static void MergeSettings(const Settings& settings, const std::string& section,
2929
if (auto* value = FindKey(settings.command_line_options, name)) {
3030
fn(Source(SettingsSpan(*value)));
3131
}
32+
if (auto* value = FindKey(settings.rw_settings, name)) {
33+
fn(Source(SettingsSpan(value)));
34+
}
3235
if (!section.empty()) {
3336
if (auto* map = FindKey(settings.ro_config, section)) {
3437
if (auto* value = FindKey(*map, name)) {

src/util/settings.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ namespace util {
2020
//! substituted if there's a need to move away from UniValue.
2121
using SettingsValue = UniValue;
2222

23-
//! Stored bitcoin settings. This struct combines settings from the command line
24-
//! and a read-only configuration file.
23+
//! Stored bitcoin settings. This struct combines settings from the command line,
24+
//! a read-only configuration file, and a read-write runtime settings file.
2525
struct Settings {
2626
std::map<std::string, SettingsValue> forced_settings;
2727
std::map<std::string, std::vector<SettingsValue>> command_line_options;
28+
std::map<std::string, SettingsValue> rw_settings;
2829
std::map<std::string, std::map<std::string, std::vector<SettingsValue>>> ro_config;
2930
};
3031

3132
//! Get settings value from combined sources: forced settings, command line
32-
//! arguments and the read-only config file.
33+
//! arguments, runtime read-write settings, and the read-only config file.
3334
//!
3435
//! @param ignore_default_section_config - ignore values set in the top-level
3536
//! section of the config file.

src/util/system.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,72 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const
381381
return !ArgsManagerHelper::Get(*this, strArg).isNull();
382382
}
383383

384+
static fs::path GetSettingsFile(bool temp = false)
385+
{
386+
return fs::absolute(temp ? "settings.json.tmp" : "settings.json", GetDataDir(/* net_specific= */ true));
387+
}
388+
389+
bool ArgsManager::ReadSettingsFile()
390+
{
391+
fsbridge::ifstream file;
392+
fs::path filepath = GetSettingsFile(/* temp= */ false);
393+
file.open(filepath);
394+
if (!file.is_open()) return true; // Ok for file not to exist.
395+
396+
util::SettingsValue in;
397+
if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
398+
LogPrintf("Error: Unable to parse settings file %s\n", filepath.string());
399+
return false;
400+
}
401+
if (file.fail()) {
402+
LogPrintf("Error reading settings file %s\n", filepath.string());
403+
return false;
404+
}
405+
file.close();
406+
407+
if (!in.isObject()) {
408+
LogPrintf("Error: Settings file %s is not in expected key-value format.\n", filepath.string());
409+
return false;
410+
}
411+
412+
LOCK(cs_args);
413+
m_settings.rw_settings.clear();
414+
const std::vector<std::string>& keys = in.getKeys();
415+
const std::vector<UniValue>& values = in.getValues();
416+
for (size_t i = 0; i < keys.size(); ++i) {
417+
m_settings.rw_settings.emplace(keys[i], values[i]);
418+
}
419+
return true;
420+
}
421+
422+
bool ArgsManager::WriteSettingsFile() const
423+
{
424+
util::SettingsValue out(util::SettingsValue::VOBJ);
425+
{
426+
LOCK(cs_args);
427+
for (const auto& value : m_settings.rw_settings) {
428+
out.__pushKV(value.first, value.second);
429+
}
430+
}
431+
432+
fsbridge::ofstream file;
433+
fs::path filepath_tmp = GetSettingsFile(/* temp= */ true);
434+
file.open(filepath_tmp);
435+
if (file.fail()) {
436+
LogPrintf("Error: Unable to open settings file %s for writing\n", filepath_tmp.string());
437+
return false;
438+
}
439+
file << out.write(/* prettyIndent= */ 1, /* indentLevel= */ 4) << std::endl;
440+
file.close();
441+
442+
fs::path filepath = GetSettingsFile(/* temp= */ false);
443+
if (!RenameOver(filepath_tmp, filepath)) {
444+
LogPrintf("Error: Unable to rename settings file %s to %s\n", filepath_tmp.string(), filepath.string());
445+
return false;
446+
}
447+
return true;
448+
}
449+
384450
bool ArgsManager::IsArgNegated(const std::string& strArg) const
385451
{
386452
return ArgsManagerHelper::Get(*this, strArg).isFalse();

src/util/system.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,27 @@ class ArgsManager
291291
* Check whether we know of this arg
292292
*/
293293
bool IsArgKnown(const std::string& key) const;
294+
295+
/**
296+
* Load <datadir>/settings.json file with saved settings. This needs to be
297+
* called after SelectParams() because the settings file is network-specific.
298+
*/
299+
bool ReadSettingsFile();
300+
301+
/**
302+
* Save <datadir>/settings.json file with persistent settings.
303+
*/
304+
bool WriteSettingsFile() const;
305+
306+
/**
307+
* Access settings with lock held.
308+
*/
309+
template <typename Fn>
310+
void LockSettings(Fn&& fn)
311+
{
312+
LOCK(cs_args);
313+
fn(m_settings);
314+
}
294315
};
295316

296317
extern ArgsManager gArgs;

0 commit comments

Comments
 (0)