Skip to content

fix(qt): report malformed lang/font settings instead of aborting at startup - #7789

Merged
PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/report-malformed-lang-font-settings-instead-of-a
Oct 4, 2026
Merged

PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/report-malformed-lang-font-settings-instead-of-a

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

If settings.json contains a "lang", "font-family", "font-scale", "font-weight-normal" or "font-weight-bold" value of an unexpected JSON type (an object or an array, e.g. after a hand edit or a bad merge of the file), dash-qt is terminated at startup by an uncaught exception (std::terminate, exit 134). It should instead show the existing "Could not read setting ... Settings file ... might be corrupt or invalid." error, which is what already happens for the same mistake in prune, proxy, onion, dbcache and the other settings handled by the getOption() loop in OptionsModel::Init.

The conversion helpers (SettingToString / SettingToInt) fall back to UniValue::get_str() for anything that is not null, a bool or a number, so an object or array throws UniValue::type_error. OptionsModel::Init already catches that for the settings read in its getOption() loop (added with bitcoin-core/gui#602), but a few reads happen outside that loop:

  • "lang", read at the top of OptionsModel::Init (same as upstream), and also read earlier through gArgs.GetArg("-lang") in GetLangTerritory(), which is called again right after common::InitConfig has loaded settings.json.
  • The Dash font settings (font-family, font-scale, font-weight-normal, font-weight-bold), read before the loop once fonts are loaded.
  • When -font-family is given on the command line and the weight is not, Init does not read the stored font-weight-normal / font-weight-bold at all. A malformed weight then gets through Init, and GuiMain throws on it a moment later from gArgs.IsArgSet("-font-weight-normal") / gArgs.GetIntArg(...), which also see settings.json.

None of these call sites is inside a try block. The only catch in GuiMain starts after the options model has been created, so the exception ends in std::terminate.

Why this is a real problem

Code path at c14104b:

Reproduction with dash-qt built from unmodified c14104b (macOS, --with-gui, debug build):

dash-qt with {"lang": {}} in settings.json (unfixed)
$ cat $DATADIR/regtest/settings.json
{"lang": {}}
$ timeout 60 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash
std::terminate() called due to unhandled exception
Exception: type=UniValue::type_error, what="JSON value of type object is not of expected type string"
exit status: 134

No debug.log is created: the abort happens in the second initTranslations() call, before the node is created.

dash-qt with {"font-weight-normal": []} in settings.json and -font-family on the command line (unfixed)
$ cat $DATADIR/regtest/settings.json
{"font-weight-normal": []}
$ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash -font-family=Montserrat
std::terminate() called due to unhandled exception
Exception: type=UniValue::type_error, what="JSON value of type array is not of expected type string"
exit status: 134

The new OptionTests cases, run against the unfixed OptionsModel::Init:

test_dash-qt (unfixed), default minimal platform
$ src/qt/test/test_dash-qt
********* Start testing of OptionTests *********
PASS   : OptionTests::initTestCase()
PASS   : OptionTests::migrateSettings()
PASS   : OptionTests::integerGetArgBug()
SKIP   : OptionTests::invalidFontSetting() AppTests cannot initialize fonts with the 'minimal' platform plugin.
FAIL!  : OptionTests::invalidLangSetting() Caught unhandled exception
   Loc: [qtestcase.cpp(1966)]
Totals: 3 passed, 1 failed, 1 skipped, 0 blacklisted, 9ms
********* Finished testing of OptionTests *********
exit status: 134
test_dash-qt (unfixed), QT_QPA_PLATFORM=cocoa so fonts are loaded
$ QT_QPA_PLATFORM=cocoa src/qt/test/test_dash-qt
********* Start testing of OptionTests *********
PASS   : OptionTests::initTestCase()
PASS   : OptionTests::migrateSettings()
PASS   : OptionTests::integerGetArgBug()
FAIL!  : OptionTests::invalidFontSetting() Caught unhandled exception
   Loc: [qtestcase.cpp(1966)]
Totals: 3 passed, 1 failed, 0 skipped, 0 blacklisted, 9ms
********* Finished testing of OptionTests *********
libc++abi: terminating due to uncaught exception of type UniValue::type_error: JSON value of type array is not of expected type string
exit status: 134

Why it matters

The user gets a crash with no explanation instead of an error that names the bad setting and the file. The way out (start with -nosettings or fix settings.json by hand) is not discoverable from an abort. It only affects startup of dash-qt with a malformed local settings.json; dashd is not affected and nothing reaches this path from the network.

What was done?

  • OptionsModel::Init: the "lang" read and the four font reads are wrapped in try/catch and report the error the same way as the existing getOption() loop. The error formatting is moved into a small local lambda (setting_error) so the loop and the new call sites share one message; the translatable string is unchanged.
  • OptionsModel::Init: the stored font weights are now always read (and so checked), and the existing condition then picks either that value or the font's default weight as before. This closes the -font-family path above.
  • GetLangTerritory(): if reading -lang throws, keep the language picked from QSettings / the system locale. Startup then continues to OptionsModel::Init, which reports the bad "lang" value through the normal error dialog.
  • OptionTests: invalidLangSetting and invalidFontSetting put an object/array into the persistent settings and check that Init returns false with the expected error. invalidFontSetting also covers the weights with -font-family set on the command line. The font case needs loaded fonts, so like updateFontsWithPixelSizedWidget it is skipped on the minimal platform.

Why this is the correct minimal fix

The root cause is that these reads sit outside the error handling that bitcoin-core/gui#602 added for exactly this kind of type error, so the fix puts them under the same handling and reuses the same message. For the font weights only the SettingToInt read is inside the try.

The font settings are not simply added to the getOption() loop: that loop also calls addOverriddenOption(), which the font blocks already do, so the overridden-options list would get duplicate entries. Reading the stored weights unconditionally does not change which weight is applied; it only makes sure that a value GuiMain will read through gArgs anyway has been checked first.

Fixing only OptionsModel::Init is not enough for "lang": GetLangTerritory() reads the same value first and would still abort before Init runs. A type check there would need to duplicate which UniValue types SettingToString accepts, while catching the conversion error keeps the existing -lang behaviour (strings, -nolang, numbers) unchanged. Making the conversion helpers non-throwing or widening GuiMain's try would be larger changes; the latter would also only swap the abort for a generic "runaway exception" message.

Behaviour for valid settings is unchanged. If the bold weight is malformed after the normal weight has been reset via forceSetting, that partial state is not undone; Init returns false and startup stops anyway.

The "lang" handling exists the same way in Bitcoin Core and could also be sent upstream.

How Has This Been Tested?

macOS arm64, --with-gui --enable-debug --enable-werror, depends Qt 5.

  • Before the fix: the two dash-qt reproductions and the two test_dash-qt runs above (abort, exit 134).

  • With the try/catch changes but without always reading the stored weights, the -font-family cases of invalidFontSetting fail (cocoa):

    FAIL!  : OptionTests::invalidFontSetting() '!OptionsModel{m_node}.Init(error)' returned FALSE. ()
       Loc: [qt/test/optiontests.cpp(135)]
    
  • After the fix, src/qt/test/test_dash-qt (default minimal platform): all suites pass; in OptionTests, invalidLangSetting passes and invalidFontSetting is skipped as expected.

    SKIP   : OptionTests::invalidFontSetting() AppTests cannot initialize fonts with the 'minimal' platform plugin.
    PASS   : OptionTests::invalidLangSetting()
    ...
    Totals: 8 passed, 0 failed, 2 skipped, 0 blacklisted, 11ms
    
  • After the fix, QT_QPA_PLATFORM=cocoa src/qt/test/test_dash-qt: all OptionTests pass, including both new cases.

    PASS   : OptionTests::invalidFontSetting()
    PASS   : OptionTests::invalidLangSetting()
    ...
    Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 15ms
    
  • After the fix, dash-qt with {"lang": {}}: no abort; it reports the error and shows the dialog (the process was then killed by timeout while the modal dialog was open):

    $ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash
    Error: Could not read setting "lang", JSON value of type object is not of expected type string.
    Settings file "<datadir>/regtest/settings.json" might be corrupt or invalid.
    exit status: 124
    
  • After the fix, dash-qt with {"font-weight-normal": []} and -font-family=Montserrat: same, no abort:

    $ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash -font-family=Montserrat
    Error: Could not read setting "font-weight-normal", JSON value of type array is not of expected type string.
    Settings file "<datadir>/regtest/settings.json" might be corrupt or invalid.
    exit status: 124
    
  • contrib/devtools/clang-format-diff.py only suggests reflowing the moved tr() line and the test lambdas/strings, which follow the style of the surrounding code; test/lint/lint-whitespace.py is clean.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

🤖 Generated with Claude Code

…tartup

OptionsModel::Init reads "lang" and the Dash font settings
("font-family", "font-scale", "font-weight-normal", "font-weight-bold")
from settings.json before the loop that turns a univalue type error into
the "Could not read setting" error. A value of unexpected type (an object
or array) therefore threw out of Init, and dash-qt was terminated by an
uncaught exception instead of showing the corrupt settings dialog.

A bad "lang" value aborted even earlier: GetLangTerritory reads -lang via
gArgs, which includes settings.json once InitConfig has run.

The stored font weights were not read at all when -font-family was given
on the command line, so a bad value passed Init and then threw from the
gArgs.GetIntArg() calls in bitcoin.cpp.

Report these reads with the same error as the loop, always read the
stored font weights, and keep the previously selected language in
GetLangTerritory when -lang can't be read so that OptionsModel::Init gets
to report it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 25 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 83ce4edf-f8c2-4a56-be48-e1c2f4ed9eec
📥 Commits

Reviewing files that changed from the base of the PR and between 8819507 and cdeadac.

📒 Files selected for processing (4)
  • src/qt/bitcoin.cpp
  • src/qt/optionsmodel.cpp
  • src/qt/test/optiontests.cpp
  • src/qt/test/optiontests.h
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit cdeadac) · triage: low

@github-actions github-actions Bot mentioned this pull request Oct 4, 2026
3 of 5 tasks

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Phase 1 + Phase 2

Static verification at the exact head found no actionable in-scope defects: malformed language and font settings now reach the existing settings-error dialog, including stored font weights when the font family is overridden, without changing valid-setting behavior. The regression tests follow existing Qt test initialization and settings-reset patterns. No builds or tests were run in this lane; the supplied CI snapshot shows successful preliminary checks and formatting, queued arm64 container jobs, and no completed application build or test results.

Review provenance

Source: reviewer 1: glm-5.3-flash (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: low by gpt-6.1-sol (effort low) — This is a contained Qt startup error-handling fix with focused regression tests, affecting neither critical surfaces nor intricate cross-subsystem behavior.
  • Phase 1 reviewers: glm-5.3-flash — general (completed, effort high); agent phase1-reviewer
  • Phase 1 model: glm-5.3-flash — zai quota: 5h 99% left, weekly 69% left; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 15% left, 5h 100% left)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort medium); agent phase2-reviewer, gpt-6.1-sol — dash-core-commit-history (completed, effort medium); agent phase2-reviewer
Out-of-scope follow-up suggestions (1)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • dash-qt still terminates on malformed settings.json values for other settings (e.g. proxy, prune, splash) via earlier unprotected gArgs conversions — Outside the PR's narrow scope, not a false positive: src/qt/bitcoin.cpp:678 invokes parameterSetup before OptionsModel initialization and the try block at line 782; InitParameterInteraction reads proxy and prune at src/init.cpp:1020 and :1083, and the splash conversion at bitcoin.cpp:686 is also unprotected. These paths are already present at c14104b and remain unchanged. The PR specifically fixes lang and the four font settings, does not worsen or depend on these adjacent paths, and does not require broader startup exception handling to achieve that goal. This pre-existing local-settings issue does not meet the exceptional follow-up bar for retention.
    • Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.

@thepastaclaw thepastaclaw added the pastaclaw:approved thepastaclaw's latest review approved this PR label Oct 4, 2026

@PastaPastaPasta PastaPastaPasta left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved

@PastaPastaPasta
PastaPastaPasta merged commit c932b8c into dashpay:develop Oct 4, 2026
48 of 49 checks passed
@thepastaclaw thepastaclaw removed the pastaclaw:approved thepastaclaw's latest review approved this PR label Oct 4, 2026
@UdjinM6 UdjinM6 added this to the 24 milestone Oct 6, 2026
PastaPastaPasta added a commit that referenced this pull request Oct 7, 2026
…aborting at startup

cdeadac fix(qt): report malformed lang/font settings instead of aborting at startup (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  If `settings.json` contains a `"lang"`, `"font-family"`, `"font-scale"`, `"font-weight-normal"` or `"font-weight-bold"` value of an unexpected JSON type (an object or an array, e.g. after a hand edit or a bad merge of the file), dash-qt is terminated at startup by an uncaught exception (`std::terminate`, exit 134). It should instead show the existing "Could not read setting ... Settings file ... might be corrupt or invalid." error, which is what already happens for the same mistake in `prune`, `proxy`, `onion`, `dbcache` and the other settings handled by the `getOption()` loop in `OptionsModel::Init`.

  The conversion helpers (`SettingToString` / `SettingToInt`) fall back to `UniValue::get_str()` for anything that is not null, a bool or a number, so an object or array throws `UniValue::type_error`. `OptionsModel::Init` already catches that for the settings read in its `getOption()` loop (added with bitcoin-core/gui#602), but a few reads happen outside that loop:

  - `"lang"`, read at the top of `OptionsModel::Init` (same as upstream), and also read earlier through `gArgs.GetArg("-lang")` in `GetLangTerritory()`, which is called again right after `common::InitConfig` has loaded `settings.json`.
  - The Dash font settings (`font-family`, `font-scale`, `font-weight-normal`, `font-weight-bold`), read before the loop once fonts are loaded.
  - When `-font-family` is given on the command line and the weight is not, `Init` does not read the stored `font-weight-normal` / `font-weight-bold` at all. A malformed weight then gets through `Init`, and `GuiMain` throws on it a moment later from `gArgs.IsArgSet("-font-weight-normal")` / `gArgs.GetIntArg(...)`, which also see `settings.json`.

  None of these call sites is inside a `try` block. The only `catch` in `GuiMain` starts after the options model has been created, so the exception ends in `std::terminate`.

  ### Why this is a real problem

  Code path at c14104b:

  - `SettingToString` / `SettingToInt` fall back to `get_str()`: [src/util/system.cpp#L529-L536](https://github.com/dashpay/dash/blob/c14104b172a2/src/util/system.cpp#L529-L536), [#L554-L561](https://github.com/dashpay/dash/blob/c14104b172a2/src/util/system.cpp#L554-L561)
  - Unprotected `"lang"` read: [src/qt/optionsmodel.cpp#L208](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/optionsmodel.cpp#L208)
  - Unprotected font reads: [src/qt/optionsmodel.cpp#L258-L302](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/optionsmodel.cpp#L258-L302); the stored weights are skipped when only `-font-family` is overridden: [#L281-L282](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/optionsmodel.cpp#L281-L282), [#L297-L298](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/optionsmodel.cpp#L297-L298)
  - `GuiMain` reading the weights through `gArgs` after `Init`: [src/qt/bitcoin.cpp#L700-L701](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L700-L701), [#L709-L710](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L709-L710)
  - The loop that does handle type errors: [src/qt/optionsmodel.cpp#L355-L370](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/optionsmodel.cpp#L355-L370)
  - `GetLangTerritory()` reading `-lang` through `gArgs`: [src/qt/bitcoin.cpp#L122](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L122), called again after `InitConfig` at [#L621](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L621) / [#L644](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L644)
  - `createOptionsModel` only handles a `false` return, not an exception: [src/qt/bitcoin.cpp#L248-L265](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L248-L265), called at [#L687](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L687), before the `try` at [#L777](https://github.com/dashpay/dash/blob/c14104b172a2/src/qt/bitcoin.cpp#L777)

  Reproduction with dash-qt built from unmodified c14104b (macOS, `--with-gui`, debug build):

  <details>
  <summary>dash-qt with <code>{"lang": {}}</code> in settings.json (unfixed)</summary>

  ```
  $ cat $DATADIR/regtest/settings.json
  {"lang": {}}
  $ timeout 60 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash
  std::terminate() called due to unhandled exception
  Exception: type=UniValue::type_error, what="JSON value of type object is not of expected type string"
  exit status: 134
  ```

  No `debug.log` is created: the abort happens in the second `initTranslations()` call, before the node is created.

  </details>

  <details>
  <summary>dash-qt with <code>{"font-weight-normal": []}</code> in settings.json and <code>-font-family</code> on the command line (unfixed)</summary>

  ```
  $ cat $DATADIR/regtest/settings.json
  {"font-weight-normal": []}
  $ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash -font-family=Montserrat
  std::terminate() called due to unhandled exception
  Exception: type=UniValue::type_error, what="JSON value of type array is not of expected type string"
  exit status: 134
  ```

  </details>

  The new `OptionTests` cases, run against the unfixed `OptionsModel::Init`:

  <details>
  <summary>test_dash-qt (unfixed), default <code>minimal</code> platform</summary>

  ```
  $ src/qt/test/test_dash-qt
  ********* Start testing of OptionTests *********
  PASS   : OptionTests::initTestCase()
  PASS   : OptionTests::migrateSettings()
  PASS   : OptionTests::integerGetArgBug()
  SKIP   : OptionTests::invalidFontSetting() AppTests cannot initialize fonts with the 'minimal' platform plugin.
  FAIL!  : OptionTests::invalidLangSetting() Caught unhandled exception
     Loc: [qtestcase.cpp(1966)]
  Totals: 3 passed, 1 failed, 1 skipped, 0 blacklisted, 9ms
  ********* Finished testing of OptionTests *********
  exit status: 134
  ```

  </details>

  <details>
  <summary>test_dash-qt (unfixed), <code>QT_QPA_PLATFORM=cocoa</code> so fonts are loaded</summary>

  ```
  $ QT_QPA_PLATFORM=cocoa src/qt/test/test_dash-qt
  ********* Start testing of OptionTests *********
  PASS   : OptionTests::initTestCase()
  PASS   : OptionTests::migrateSettings()
  PASS   : OptionTests::integerGetArgBug()
  FAIL!  : OptionTests::invalidFontSetting() Caught unhandled exception
     Loc: [qtestcase.cpp(1966)]
  Totals: 3 passed, 1 failed, 0 skipped, 0 blacklisted, 9ms
  ********* Finished testing of OptionTests *********
  libc++abi: terminating due to uncaught exception of type UniValue::type_error: JSON value of type array is not of expected type string
  exit status: 134
  ```

  </details>

  ### Why it matters

  The user gets a crash with no explanation instead of an error that names the bad setting and the file. The way out (start with `-nosettings` or fix `settings.json` by hand) is not discoverable from an abort. It only affects startup of dash-qt with a malformed local `settings.json`; dashd is not affected and nothing reaches this path from the network.

  ## What was done?

  - `OptionsModel::Init`: the `"lang"` read and the four font reads are wrapped in `try`/`catch` and report the error the same way as the existing `getOption()` loop. The error formatting is moved into a small local lambda (`setting_error`) so the loop and the new call sites share one message; the translatable string is unchanged.
  - `OptionsModel::Init`: the stored font weights are now always read (and so checked), and the existing condition then picks either that value or the font's default weight as before. This closes the `-font-family` path above.
  - `GetLangTerritory()`: if reading `-lang` throws, keep the language picked from QSettings / the system locale. Startup then continues to `OptionsModel::Init`, which reports the bad `"lang"` value through the normal error dialog.
  - `OptionTests`: `invalidLangSetting` and `invalidFontSetting` put an object/array into the persistent settings and check that `Init` returns `false` with the expected error. `invalidFontSetting` also covers the weights with `-font-family` set on the command line. The font case needs loaded fonts, so like `updateFontsWithPixelSizedWidget` it is skipped on the `minimal` platform.

  ### Why this is the correct minimal fix

  The root cause is that these reads sit outside the error handling that bitcoin-core/gui#602 added for exactly this kind of type error, so the fix puts them under the same handling and reuses the same message. For the font weights only the `SettingToInt` read is inside the `try`.

  The font settings are not simply added to the `getOption()` loop: that loop also calls `addOverriddenOption()`, which the font blocks already do, so the overridden-options list would get duplicate entries. Reading the stored weights unconditionally does not change which weight is applied; it only makes sure that a value `GuiMain` will read through `gArgs` anyway has been checked first.

  Fixing only `OptionsModel::Init` is not enough for `"lang"`: `GetLangTerritory()` reads the same value first and would still abort before `Init` runs. A type check there would need to duplicate which `UniValue` types `SettingToString` accepts, while catching the conversion error keeps the existing `-lang` behaviour (strings, `-nolang`, numbers) unchanged. Making the conversion helpers non-throwing or widening `GuiMain`'s `try` would be larger changes; the latter would also only swap the abort for a generic "runaway exception" message.

  Behaviour for valid settings is unchanged. If the bold weight is malformed after the normal weight has been reset via `forceSetting`, that partial state is not undone; `Init` returns `false` and startup stops anyway.

  The `"lang"` handling exists the same way in Bitcoin Core and could also be sent upstream.

  ## How Has This Been Tested?

  macOS arm64, `--with-gui --enable-debug --enable-werror`, depends Qt 5.

  - Before the fix: the two dash-qt reproductions and the two `test_dash-qt` runs above (abort, exit 134).
  - With the `try`/`catch` changes but without always reading the stored weights, the `-font-family` cases of `invalidFontSetting` fail (cocoa):

    ```
    FAIL!  : OptionTests::invalidFontSetting() '!OptionsModel{m_node}.Init(error)' returned FALSE. ()
       Loc: [qt/test/optiontests.cpp(135)]
    ```

  - After the fix, `src/qt/test/test_dash-qt` (default `minimal` platform): all suites pass; in `OptionTests`, `invalidLangSetting` passes and `invalidFontSetting` is skipped as expected.

    ```
    SKIP   : OptionTests::invalidFontSetting() AppTests cannot initialize fonts with the 'minimal' platform plugin.
    PASS   : OptionTests::invalidLangSetting()
    ...
    Totals: 8 passed, 0 failed, 2 skipped, 0 blacklisted, 11ms
    ```

  - After the fix, `QT_QPA_PLATFORM=cocoa src/qt/test/test_dash-qt`: all `OptionTests` pass, including both new cases.

    ```
    PASS   : OptionTests::invalidFontSetting()
    PASS   : OptionTests::invalidLangSetting()
    ...
    Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 15ms
    ```

  - After the fix, dash-qt with `{"lang": {}}`: no abort; it reports the error and shows the dialog (the process was then killed by `timeout` while the modal dialog was open):

    ```
    $ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash
    Error: Could not read setting "lang", JSON value of type object is not of expected type string.
    Settings file "<datadir>/regtest/settings.json" might be corrupt or invalid.
    exit status: 124
    ```

  - After the fix, dash-qt with `{"font-weight-normal": []}` and `-font-family=Montserrat`: same, no abort:

    ```
    $ timeout 20 src/qt/dash-qt -regtest -datadir=$DATADIR -nosplash -font-family=Montserrat
    Error: Could not read setting "font-weight-normal", JSON value of type array is not of expected type string.
    Settings file "<datadir>/regtest/settings.json" might be corrupt or invalid.
    exit status: 124
    ```

  - `contrib/devtools/clang-format-diff.py` only suggests reflowing the moved `tr()` line and the test lambdas/strings, which follow the style of the surrounding code; `test/lint/lint-whitespace.py` is clean.

  ## Breaking Changes

  None.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have made corresponding changes to the documentation
  - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

Top commit has no ACKs.

Tree-SHA512: 69ce7789a69d196e7934f802450df2f8d40dc02fe7c7490c46eb5a86cf8674d6272472476a71d5c9984dfad146c78f6dcbee9731ad55f24b25d3830aeba933b3
(cherry picked from commit c932b8c)
PastaPastaPasta added a commit that referenced this pull request Oct 7, 2026
fbe6526 Merge #7807: feat(consensus): hold EvoNode shares and multiple payouts behind a new evo_shares deployment (pasta)
57fd2d9 Merge #7776: fix: keep a pending ProUpServTx across a registrar update that keeps the operator key (pasta)
ca69801 Merge #7803: fix: return non-zero amount of accounts after sethdseed (pasta)
57a8f07 Merge #7796: backport: bitcoin#26720 (remaining), partial bitcoin#29523: weight-check the knapsack exact-match selection (pasta)
4eb82f6 Merge #7786: fix(net): keep a dedicated onion listener when -bind is given and correct the bind release note (pasta)
70626a0 Merge #7793: backport: partial bitcoin#28868 (wallet: reload wallet if migration exited early, keep mixed watchonly txs) (pasta)
250a3cd Merge #7790: fix: guard cached governance object flags with the object lock (pasta)
700c195 Merge #7787: fix: only sign EHF signals inside the deployment's start/timeout window (pasta)
a3b0f3c Merge #7785: fix: restore the txindex requirement when governance validation is enabled (pasta)
2d1735d Merge #7794: fix(wallet): floor the denomination gap threshold so rebalancing cannot oscillate (pasta)
d9f6592 Merge #7783: fix: stop fish completion from re-running the typed dash-cli command (pasta)
89d44c8 Merge #7795: backport: bitcoin#34272, partial bitcoin#31650 (pass PSBT by const reference in PSBTInputSignedAndVerified) (pasta)
a1d7411 Merge #7779: fix!: report legacy evonode Platform addresses when listdiff changes its address (pasta)
73f6ea2 Merge #7789: fix(qt): report malformed lang/font settings instead of aborting at startup (pasta)
40de493 Merge #7781: fix(rpc): use a consistent tip height in quorum dkgstatus (pasta)
7d1a90d Merge #7791: fix(net): request object votes from nPeersPerHashMax peers (pasta)
51b145c Merge #7778: test: use single-member in functional tests: feature_mnehf, feature_notifications, p2p_instantsend (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  Backports for v24.0.0-rc.3. `v24.0.x` was cut at the v24.0.0-rc.2 tag (1e239d4), and develop has since moved to 24.1.0 (#7775), so rc.3 is built from this branch instead of being tagged on develop like rc.1 and rc.2.

  ## What was done?

  Cherry-picked (`git cherry-pick -m1 -x`) every develop merge since rc.2 that carries `backport-candidate-24.0.x`, plus #7778, which #7807's test needs. They are in develop merge order:

  - #7778 test: use single-member in functional tests: feature_mnehf, feature_notifications, p2p_instantsend (test-only. Needed because #7807's `feature_evo_shares_activation.py` depends on its `mine_quorum_single_member()` fix and fails without it.)
  - #7791 fix(net): request object votes from nPeersPerHashMax peers
  - #7781 fix(rpc): use a consistent tip height in quorum dkgstatus
  - #7789 fix(qt): report malformed lang/font settings instead of aborting at startup
  - #7779 fix!: report legacy evonode Platform addresses when listdiff changes its address
  - #7795 backport: bitcoin#34272, partial bitcoin#31650 (pass PSBT by const reference in PSBTInputSignedAndVerified)
  - #7783 fix: stop fish completion from re-running the typed dash-cli command
  - #7794 fix(wallet): floor the denomination gap threshold so rebalancing cannot oscillate
  - #7785 fix: restore the txindex requirement when governance validation is enabled
  - #7787 fix: only sign EHF signals inside the deployment's start/timeout window
  - #7790 fix: guard cached governance object flags with the object lock
  - #7793 backport: partial bitcoin#28868 (wallet: reload wallet if migration exited early, keep mixed watchonly txs)
  - #7786 fix(net): keep a dedicated onion listener when -bind is given and correct the bind release note. Includes partial bitcoin#36170 (5f40d56). Upstream's dedicated-onion-bind changes to `feature_torcontrol.py` and `p2p_private_broadcast.py` are not included, because those tests depend on upstream commits (5693833, e74d54e) that Dash does not have.
  - #7796 backport: bitcoin#26720 (remaining), partial bitcoin#29523: weight-check the knapsack exact-match selection
  - #7803 fix: return non-zero amount of accounts after sethdseed
  - #7776 fix: keep a pending ProUpServTx across a registrar update that keeps the operator key
  - #7807 feat(consensus): hold EvoNode shares and multiple payouts behind a new evo_shares deployment

  One pick needed conflict resolution, in a test only, described in its commit message: #7793, `test/functional/wallet_migration.py`. `test_conflict_txs()` comes from bitcoin#28542 (#7762), which is not on this branch, so it is left out. The C++ part of #7793 is identical to develop.

  Merged since rc.2 but intentionally not included: #7775 (24.1 version bump), #7260 and #7804 (new Qt feature and its CI fix), #7584 (refactor), routine upstream backports #7257, #7759, #7762, #7768, #7788, #7801, and test/CI-only changes #7777 and #7797.

  No version change is needed. `configure.ac` is already 24.0.0 with `CLIENT_VERSION_IS_RELEASE` false, so the build takes its version string from the `v24.0.0-rc.3` tag, as it did for rc.2.

  ## How Has This Been Tested?

  Built on macOS arm64 against depends with `--enable-debug --enable-werror` (dashd, dash-qt, tests).

  - `make check`: all unit test suites pass, including `test_dash-qt`.
  - Functional tests, all passing: `feature_evo_shares_activation.py`, `feature_masternode_shares.py`, `feature_mnehf.py`, `feature_governance.py --descriptors`, `feature_governance_objects.py`, `feature_governance_txindex_devnet.py`, `feature_proxy.py`, `feature_notifications.py`, `feature_llmq_connections.py`, `feature_llmq_chainlocks_automatic.py`, `feature_llmq_singlenode.py`, `p2p_instantsend.py`, `rpc_blockchain.py` (v1 and v2 transport), `rpc_netinfo.py`, `rpc_quorum.py`, `rpc_verifychainlock.py`, `wallet_hd.py` (legacy and descriptors), `wallet_migration.py`.
  - `feature_bind_extra.py` (from #7786) only runs on Linux and was skipped locally, so CI covers it.

  Every touched file was diffed against develop. The remaining differences come only from develop PRs that are not part of this backport.

  ## Breaking Changes

  Carried over from the picked PRs:

  - #7807: consensus change behind the new `evo_shares` deployment (bit 14). Mainnet and testnet are unaffected until it activates. Devnets with `v24` already active and an EvoNode carrying several owner payouts need to check or reset. New RPC `protx shared_register_prepare_evo`.
  - #7785: outside regtest, a node with `-txindex=0` and without `-disablegovernance` refuses to start again, as before v23.1.0.
  - #7786: carries a partial bitcoin#36170. A node started with `-bind` but no specific `-bind=<addr:port>=onion` now refuses to start while `-listenonion` is enabled. That is the default when listening, even without Tor configured. A wildcard onion bind is also refused. Such nodes need `-bind=127.0.0.1:<port>=onion` or `-listenonion=0`. Nodes without `-bind`, including those using only `-whitebind`, keep the default `127.0.0.1:9996` (`19996` testnet) onion target. See `doc/release-notes-7300.md`.
  - #7779: `protx listdiff` now includes `platform_p2p` and `platform_https` for a legacy-address evonode whose core P2P address changes.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have made corresponding changes to the documentation
  - [ ] I have assigned this pull request to a milestone

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

Top commit has no ACKs.

Tree-SHA512: 8cb01bdb0234f31b5dd2c105b69673de148922991a7523627395c0e0ccfa22b94be5ffbecc842398d443bc90fd9baf2eee6a9cf6fcdbbd1626eb00b93726a55a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants