Skip to content

Commit 284f339

Browse files
ryanofskyfurszy
andcommitted
Migrate -dbcache setting from QSettings to settings.json
This is just the first of multiple settings that will be stored in the bitcoin persistent setting file and shared with bitcoind, instead of being stored as Qt settings backed by the windows registry or platform specific config files which are ignored by bitcoind. Co-Authored-By: furszy <[email protected]>
1 parent 2642dee commit 284f339

File tree

10 files changed

+146
-34
lines changed

10 files changed

+146
-34
lines changed

src/qt/bitcoin.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,26 @@ void BitcoinApplication::createPaymentServer()
259259
}
260260
#endif
261261

262-
void BitcoinApplication::createOptionsModel(bool resetSettings)
262+
bool BitcoinApplication::createOptionsModel(bool resetSettings)
263263
{
264-
optionsModel = new OptionsModel(node(), this, resetSettings);
264+
optionsModel = new OptionsModel(node(), this);
265+
if (resetSettings) {
266+
optionsModel->Reset();
267+
}
268+
bilingual_str error;
269+
if (!optionsModel->Init(error)) {
270+
fs::path settings_path;
271+
if (gArgs.GetSettingsPath(&settings_path)) {
272+
error += Untranslated("\n");
273+
std::string quoted_path = strprintf("%s", fs::quoted(fs::PathToString(settings_path)));
274+
error.original += strprintf("Settings file %s might be corrupt or invalid.", quoted_path);
275+
error.translated += tr("Settings file %1 might be corrupt or invalid.").arg(QString::fromStdString(quoted_path)).toStdString();
276+
}
277+
InitError(error);
278+
QMessageBox::critical(nullptr, PACKAGE_NAME, QString::fromStdString(error.translated));
279+
return false;
280+
}
281+
return true;
265282
}
266283

267284
void BitcoinApplication::createWindow(const NetworkStyle *networkStyle)
@@ -641,7 +658,9 @@ int GuiMain(int argc, char* argv[])
641658
app.createNode(*init);
642659

643660
// Load GUI settings from QSettings
644-
app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false));
661+
if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) {
662+
return EXIT_FAILURE;
663+
}
645664

646665
if (did_show_intro) {
647666
// Store intro dialog settings other than datadir (network specific)

src/qt/bitcoin.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class BitcoinApplication: public QApplication
4747
/// parameter interaction/setup based on rules
4848
void parameterSetup();
4949
/// Create options model
50-
void createOptionsModel(bool resetSettings);
50+
[[nodiscard]] bool createOptionsModel(bool resetSettings);
5151
/// Initialize prune setting
5252
void InitPruneSetting(int64_t prune_MiB);
5353
/// Create main window

src/qt/optionsmodel.cpp

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,41 @@
2626
#include <QStringList>
2727
#include <QVariant>
2828

29+
#include <univalue.h>
30+
2931
const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1";
3032

3133
static const QString GetDefaultProxyAddress();
3234

33-
OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) :
35+
/** Map GUI option ID to node setting name. */
36+
static const char* SettingName(OptionsModel::OptionID option)
37+
{
38+
switch (option) {
39+
case OptionsModel::DatabaseCache: return "dbcache";
40+
default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option));
41+
}
42+
}
43+
44+
/** Call node.updateRwSetting() with Bitcoin 22.x workaround. */
45+
static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value)
46+
{
47+
if (value.isNum() && option == OptionsModel::DatabaseCache) {
48+
// Write certain old settings as strings, even though they are numbers,
49+
// because Bitcoin 22.x releases try to read these specific settings as
50+
// strings in addOverriddenOption() calls at startup, triggering
51+
// uncaught exceptions in UniValue::get_str(). These errors were fixed
52+
// in later releases by https://github.com/bitcoin/bitcoin/pull/24498.
53+
// If new numeric settings are added, they can be written as numbers
54+
// instead of strings, because bitcoin 22.x will not try to read these.
55+
node.updateRwSetting(SettingName(option), value.getValStr());
56+
} else {
57+
node.updateRwSetting(SettingName(option), value);
58+
}
59+
}
60+
61+
OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) :
3462
QAbstractListModel(parent), m_node{node}
3563
{
36-
Init(resetSettings);
3764
}
3865

3966
void OptionsModel::addOverriddenOption(const std::string &option)
@@ -42,11 +69,8 @@ void OptionsModel::addOverriddenOption(const std::string &option)
4269
}
4370

4471
// Writes all missing QSettings with their default values
45-
void OptionsModel::Init(bool resetSettings)
72+
bool OptionsModel::Init(bilingual_str& error)
4673
{
47-
if (resetSettings)
48-
Reset();
49-
5074
checkAndMigrate();
5175

5276
QSettings settings;
@@ -98,7 +122,20 @@ void OptionsModel::Init(bool resetSettings)
98122

99123
// These are shared with the core or have a command-line parameter
100124
// and we want command-line parameters to overwrite the GUI settings.
101-
//
125+
for (OptionID option : {DatabaseCache}) {
126+
std::string setting = SettingName(option);
127+
if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting);
128+
try {
129+
getOption(option);
130+
} catch (const std::exception& e) {
131+
// This handles exceptions thrown by univalue that can happen if
132+
// settings in settings.json don't have the expected types.
133+
error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what());
134+
error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString();
135+
return false;
136+
}
137+
}
138+
102139
// If setting doesn't exist create it with defaults.
103140
//
104141
// If gArgs.SoftSetArg() or gArgs.SoftSetBoolArg() return false we were overridden
@@ -111,11 +148,6 @@ void OptionsModel::Init(bool resetSettings)
111148
settings.setValue("nPruneSize", DEFAULT_PRUNE_TARGET_GB);
112149
SetPruneEnabled(settings.value("bPrune").toBool());
113150

114-
if (!settings.contains("nDatabaseCache"))
115-
settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache);
116-
if (!gArgs.SoftSetArg("-dbcache", settings.value("nDatabaseCache").toString().toStdString()))
117-
addOverriddenOption("-dbcache");
118-
119151
if (!settings.contains("nThreadsScriptVerif"))
120152
settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS);
121153
if (!gArgs.SoftSetArg("-par", settings.value("nThreadsScriptVerif").toString().toStdString()))
@@ -222,6 +254,8 @@ void OptionsModel::Init(bool resetSettings)
222254
}
223255
m_use_embedded_monospaced_font = settings.value("UseEmbeddedMonospacedFont").toBool();
224256
Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font);
257+
258+
return true;
225259
}
226260

227261
/** Helper function to copy contents from one QSettings to another.
@@ -356,6 +390,8 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
356390

357391
QVariant OptionsModel::getOption(OptionID option) const
358392
{
393+
auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); };
394+
359395
QSettings settings;
360396
switch (option) {
361397
case StartAtStartup:
@@ -420,7 +456,7 @@ QVariant OptionsModel::getOption(OptionID option) const
420456
case PruneSize:
421457
return settings.value("nPruneSize");
422458
case DatabaseCache:
423-
return settings.value("nDatabaseCache");
459+
return qlonglong(SettingToInt(setting(), nDefaultDbCache));
424460
case ThreadsScriptVerif:
425461
return settings.value("nThreadsScriptVerif");
426462
case Listen:
@@ -434,6 +470,9 @@ QVariant OptionsModel::getOption(OptionID option) const
434470

435471
bool OptionsModel::setOption(OptionID option, const QVariant& value)
436472
{
473+
auto changed = [&] { return value.isValid() && value != getOption(option); };
474+
auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); };
475+
437476
bool successful = true; /* set to false on parse error */
438477
QSettings settings;
439478

@@ -574,8 +613,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value)
574613
}
575614
break;
576615
case DatabaseCache:
577-
if (settings.value("nDatabaseCache") != value) {
578-
settings.setValue("nDatabaseCache", value);
616+
if (changed()) {
617+
update(static_cast<int64_t>(value.toLongLong()));
579618
setRestartRequired(true);
580619
}
581620
break;
@@ -654,4 +693,16 @@ void OptionsModel::checkAndMigrate()
654693
if (settings.contains("addrSeparateProxyTor") && settings.value("addrSeparateProxyTor").toString().endsWith("%2")) {
655694
settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress());
656695
}
696+
697+
// Migrate and delete legacy GUI settings that have now moved to <datadir>/settings.json.
698+
auto migrate_setting = [&](OptionID option, const QString& qt_name) {
699+
if (!settings.contains(qt_name)) return;
700+
QVariant value = settings.value(qt_name);
701+
if (node().getPersistentSetting(SettingName(option)).isNull()) {
702+
setOption(option, value);
703+
}
704+
settings.remove(qt_name);
705+
};
706+
707+
migrate_setting(DatabaseCache, "nDatabaseCache");
657708
}

src/qt/optionsmodel.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <assert.h>
1515

16+
struct bilingual_str;
1617
namespace interfaces {
1718
class Node;
1819
}
@@ -41,7 +42,7 @@ class OptionsModel : public QAbstractListModel
4142
Q_OBJECT
4243

4344
public:
44-
explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false);
45+
explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr);
4546

4647
enum OptionID {
4748
StartAtStartup, // bool
@@ -74,7 +75,7 @@ class OptionsModel : public QAbstractListModel
7475
OptionIDRowCount,
7576
};
7677

77-
void Init(bool resetSettings = false);
78+
bool Init(bilingual_str& error);
7879
void Reset();
7980

8081
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
@@ -120,6 +121,7 @@ class OptionsModel : public QAbstractListModel
120121
bool fCoinControlFeatures;
121122
bool m_sub_fee_from_amount;
122123
bool m_enable_psbt_controls;
124+
123125
/* settings that were overridden by command-line */
124126
QString strOverriddenByCommandLine;
125127

@@ -128,6 +130,7 @@ class OptionsModel : public QAbstractListModel
128130

129131
// Check settings version and upgrade default values if required
130132
void checkAndMigrate();
133+
131134
Q_SIGNALS:
132135
void displayUnitChanged(BitcoinUnit unit);
133136
void coinControlFeaturesChanged(bool);

src/qt/test/addressbooktests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
128128
// Initialize relevant QT models.
129129
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
130130
OptionsModel optionsModel(node);
131+
bilingual_str error;
132+
QVERIFY(optionsModel.Init(error));
131133
ClientModel clientModel(node, &optionsModel);
132134
WalletContext& context = *node.walletLoader().context();
133135
AddWallet(context, wallet);

src/qt/test/apptests.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,9 @@ void AppTests::appTests()
7070
}
7171
#endif
7272

73-
fs::create_directories([] {
74-
BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to
75-
return gArgs.GetDataDirNet() / "blocks";
76-
}());
77-
7873
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
7974
m_app.parameterSetup();
80-
m_app.createOptionsModel(true /* reset settings */);
75+
QVERIFY(m_app.createOptionsModel(true /* reset settings */));
8176
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().NetworkIDString()));
8277
m_app.setupPlatformStyle();
8378
m_app.createWindow(style.data());

src/qt/test/optiontests.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,40 @@
1313

1414
#include <univalue.h>
1515

16+
#include <fstream>
17+
18+
OptionTests::OptionTests(interfaces::Node& node) : m_node(node)
19+
{
20+
gArgs.LockSettings([&](util::Settings& s) { m_previous_settings = s; });
21+
}
22+
23+
void OptionTests::init()
24+
{
25+
// reset args
26+
gArgs.LockSettings([&](util::Settings& s) { s = m_previous_settings; });
27+
gArgs.ClearPathCache();
28+
}
29+
30+
void OptionTests::migrateSettings()
31+
{
32+
// Set legacy QSettings and verify that they get cleared and migrated to
33+
// settings.json
34+
QSettings settings;
35+
settings.setValue("nDatabaseCache", 600);
36+
37+
settings.sync();
38+
39+
OptionsModel options{m_node};
40+
bilingual_str error;
41+
QVERIFY(options.Init(error));
42+
QVERIFY(!settings.contains("nDatabaseCache"));
43+
44+
std::ifstream file(gArgs.GetDataDirNet() / "settings.json");
45+
QCOMPARE(std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()).c_str(), "{\n"
46+
" \"dbcache\": \"600\"\n"
47+
"}\n");
48+
}
49+
1650
void OptionTests::integerGetArgBug()
1751
{
1852
// Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure
@@ -23,7 +57,8 @@ void OptionTests::integerGetArgBug()
2357
settings.rw_settings["prune"] = 3814;
2458
});
2559
gArgs.WriteSettingsFile();
26-
OptionsModel{m_node};
60+
bilingual_str error;
61+
QVERIFY(OptionsModel{m_node}.Init(error));
2762
gArgs.LockSettings([&](util::Settings& settings) {
2863
settings.rw_settings.erase("prune");
2964
});
@@ -36,8 +71,6 @@ void OptionTests::parametersInteraction()
3671
// It was fixed via https://github.com/bitcoin-core/gui/pull/568.
3772
// With fListen=false in ~/.config/Bitcoin/Bitcoin-Qt.conf and all else left as default,
3873
// bitcoin-qt should set both -listen and -listenonion to false and start successfully.
39-
gArgs.ClearPathCache();
40-
4174
gArgs.LockSettings([&](util::Settings& s) {
4275
s.forced_settings.erase("listen");
4376
s.forced_settings.erase("listenonion");
@@ -48,7 +81,8 @@ void OptionTests::parametersInteraction()
4881
QSettings settings;
4982
settings.setValue("fListen", false);
5083

51-
OptionsModel{m_node};
84+
bilingual_str error;
85+
QVERIFY(OptionsModel{m_node}.Init(error));
5286

5387
const bool expected{false};
5488

src/qt/test/optiontests.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,26 @@
66
#define BITCOIN_QT_TEST_OPTIONTESTS_H
77

88
#include <qt/optionsmodel.h>
9+
#include <univalue.h>
10+
#include <util/settings.h>
911

1012
#include <QObject>
1113

1214
class OptionTests : public QObject
1315
{
1416
Q_OBJECT
1517
public:
16-
explicit OptionTests(interfaces::Node& node) : m_node(node) {}
18+
explicit OptionTests(interfaces::Node& node);
1719

1820
private Q_SLOTS:
21+
void init(); // called before each test function execution.
22+
void migrateSettings();
1923
void integerGetArgBug();
2024
void parametersInteraction();
2125

2226
private:
2327
interfaces::Node& m_node;
28+
util::Settings m_previous_settings;
2429
};
2530

2631
#endif // BITCOIN_QT_TEST_OPTIONTESTS_H

src/qt/test/test_main.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ int main(int argc, char* argv[])
5858
// regtest params.
5959
//
6060
// All tests must use their own testing setup (if needed).
61-
{
61+
fs::create_directories([] {
6262
BasicTestingSetup dummy{CBaseChainParams::REGTEST};
63-
}
63+
return gArgs.GetDataDirNet() / "blocks";
64+
}());
6465

6566
std::unique_ptr<interfaces::Init> init = interfaces::MakeGuiInit(argc, argv);
6667
gArgs.ForceSetArg("-listen", "0");

src/qt/test/wallettests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ void TestGUI(interfaces::Node& node)
185185
SendCoinsDialog sendCoinsDialog(platformStyle.get());
186186
TransactionView transactionView(platformStyle.get());
187187
OptionsModel optionsModel(node);
188+
bilingual_str error;
189+
QVERIFY(optionsModel.Init(error));
188190
ClientModel clientModel(node, &optionsModel);
189191
WalletContext& context = *node.walletLoader().context();
190192
AddWallet(context, wallet);

0 commit comments

Comments
 (0)