Skip to content

fix: restore the txindex requirement when governance validation is enabled - #7785

Merged
PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/restore-the-txindex-requirement-when-governance
Oct 5, 2026
Merged

PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/restore-the-txindex-requirement-when-governance

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Governance validation looks up each proposal's collateral transaction through the transaction index. Without -txindex, every proposal fails validation with "Invalid proposal collateral". Until v23.1.0 dashd refused to start with governance enabled and -txindex=0 outside regtest:

Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.

Commit 91129d4 ("refactor: drop dead code with ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED", merged in #7008) removed this check from LoadChainstate. Its commit message says governance is now force-disabled in this case and the "governance validation disabled" warning is shown instead. That is not the case: only -prune soft-sets -disablegovernance. An explicit -txindex=0 on a non-pruned mainnet, testnet or devnet node that is not a masternode now starts quietly with governance enabled and no transaction index. That node then rejects every proposal it is asked to submit or receives from peers, and adds misbehavior score to each peer that relays one.

Why this is a real problem

Code path at c14104b:

The reproductions below ran against an unfixed dashd: Parts A and B against c14104b, Part C against the branch rebased on 8819507 with the src/init.cpp hunk reversed (nothing between those two commits touches C++). They are small BitcoinTestFramework scripts. Part A uses devnet because regtest is exempt from the check. The framework cannot mine the devnet genesis block, so Parts B and C show the effects on regtest, where -txindex=0 with governance is allowed on every revision. The collateral lookup and peer scoring are the same on every network.

Part A (devnet): a node with -txindex=0 starts with governance enabled and no warning
$ DASH_WORKTREE=<worktree> python3 repro_a_devnet_startup.py --configfile=<worktree>/test/config.ini
TestFramework (INFO): Start a devnet node with -txindex=0 (governance not disabled)
TestFramework (INFO): RESULT: dashd started; stderr: ''
TestFramework (INFO): getindexinfo: {}
TestFramework (INFO): debug.log: ... Command-line arg: txindex="0"
TestFramework (INFO): debug.log: ... [flat-database.h:194] [Store] Verifying governance.dat format...
TestFramework (INFO): debug.log: ... [flat-database.h:200] [Store] Writing info to governance.dat...
TestFramework (INFO): debug.log: ... [flat-database.h:71] [CoreWrite]      Governance Objects: 0 (Proposals: 0, Triggers: 0, Other: 0; Erased: 0)
TestFramework (INFO): debug.log: ... [governance/governance.cpp:1105] [UpdatedBlockTip] [gobject] CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: 0
TestFramework (INFO): Start a devnet node with -txindex=0 -disablegovernance
TestFramework (INFO): RESULT: dashd started
TestFramework (INFO): RESULT: stderr was the governance-disabled warning
TestFramework (INFO): Tests successful
Part B (regtest): with governance enabled and -txindex=0, a valid proposal is rejected; the same node accepts it with -txindex=1
$ DASH_WORKTREE=<worktree> python3 repro_b_regtest_proposal.py --configfile=<worktree>/test/config.ini
TestFramework (INFO): Node started with -txindex=0; getindexinfo: {}
TestFramework (INFO): Collateral 246c979b734f002c8f7d1f472fe159f2a1541472e00fd44b6682134b36327da6 confirmations: 6
TestFramework (INFO): RESULT (-txindex=0): gobject submit rejected: Governance object is not valid - 6926f935aa79335ddac0cf52ca7665a15dc58cda80c89af9dcd22501a819fe63 - Invalid proposal collateral (-32603)
TestFramework (INFO): debug.log: ... [governance/object.cpp:804] [IsCollateralValid] CGovernanceObject::IsCollateralValid -- Can't find collateral tx 246c979b734f002c8f7d1f472fe159f2a1541472e00fd44b6682134b36327da6
TestFramework (INFO): gobject count: {'objects_total': 0, 'proposals': 0, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
TestFramework (INFO): Restart the same node with -txindex=1 and submit the same proposal
TestFramework (INFO): RESULT (-txindex=1): gobject submit accepted: 6926f935aa79335ddac0cf52ca7665a15dc58cda80c89af9dcd22501a819fe63
TestFramework (INFO): gobject count: {'objects_total': 1, 'proposals': 1, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
TestFramework (INFO): Tests successful
Part C (regtest): the -txindex=0 node scores 20 misbehavior points per valid proposal relayed by a peer and disconnects it after five

node0 (-txindex=1) connects to node1 (-txindex=0, governance enabled), so node1 sees node0 as an ordinary inbound peer. node0 then submits five valid proposals. Because node0 is on a local address, node1 disconnects it without adding the address to the discouragement list; a non-local peer is disconnected and discouraged.

$ DASH_WORKTREE=<worktree> python3 repro_c_peer_misbehaving.py --configfile=<worktree>/test/config.ini
TestFramework (INFO): node0 getindexinfo: {'txindex': {'synced': True, 'best_block_height': 0}}
TestFramework (INFO): node1 getindexinfo: {}
TestFramework (INFO): node1 sees node0 as peer=0 inbound=True connection_type=inbound permissions=[]
TestFramework (INFO): node0 accepted proposal b23bc414c1e0b465070af792c669ab4eabc529f2deee560ef4593458f01a499e
TestFramework (INFO): node0 accepted proposal c81ae2dd9fe14ac2c13eea80ad52a87df586e7be6e317a6540b240a940678ab4
TestFramework (INFO): node0 accepted proposal e6a890d8da141a791fb5cb5b4dfbde92da3e423f7258b383cc3f8e9fa1620c7b
TestFramework (INFO): node0 accepted proposal 9e7706395cfacfdbb9aad6e3762abece051a60e5db0ebbec219a9e1201284c54
TestFramework (INFO): node0 accepted proposal 499abad8125b978ba13ec4b6e19bb2bab9d9d63a5475f6e8c37bbc739e8780e3
TestFramework (INFO): node0 gobject count: {'objects_total': 5, 'proposals': 5, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
TestFramework (INFO): node1 gobject count: {'objects_total': 0, 'proposals': 0, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
TestFramework (INFO): node1 peers after relay: 0
TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (0 -> 20)
TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (20 -> 40)
TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (40 -> 60)
TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (60 -> 80)
TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (80 -> 100) DISCOURAGE THRESHOLD EXCEEDED
TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:5860] [MaybeDiscourageAndDisconnect] [net] Warning: disconnecting but not discouraging local peer 0!
TestFramework (INFO): Tests successful

Why it matters

This only affects operators who explicitly set -txindex=0 (the default is 1) on a non-pruned node that is not a masternode. Nothing at startup tells them anything is wrong, and:

  • gobject list/count, the governance view in the GUI and proposal vote tracking stay empty or incomplete, and gobject submit fails with an unhelpful collateral error.
  • The node does not relay proposals.
  • Every honest peer that relays five or more proposals to it, which on mainnet includes any peer it syncs governance objects from, reaches the discouragement threshold and is disconnected; non-local peers are also discouraged. This applies to its outbound peers as well as inbound ones, so the misconfigured node keeps losing its own connections.

Trigger and superblock validation do not use the transaction index, so block validation is not affected.

The regression was introduced in v23.1.0. It is not a v24.0.0 release-candidate regression.

What was done?

Restored the check in AppInitParameterInteraction, next to the other governance and masternode parameter checks. Outside regtest, if governance validation is enabled and -txindex is disabled, dashd now fails to start with the original error message.

Added feature_governance_txindex_devnet.py. It runs on devnet, because regtest is exempt, and checks two things: -txindex=0 alone is refused with that error, and -txindex=0 -disablegovernance still starts with the governance-disabled warning.

Why this is the correct minimal fix

  • It brings back the behavior from before 91129d4: the same condition, the same translatable message and the same regtest exemption. It is a parameter check, so it lives with the other parameter checks instead of going back into LoadChainstate. That needs no changes to the LoadChainstate signature or to ChainstateLoadingError, and it fails before the block index is loaded.
  • The regtest exemption is kept on purpose. Many regtest functional tests run with -txindex=0 and governance enabled (feature_reindex.py, rpc_blockchain.py, rpc_misc.py, feature_unsupported_utxo_db.py), as they did before the check was removed.
  • The alternative is to do what the 91129d4 commit message describes: soft-set -disablegovernance when -txindex=0. That would change behavior instead of restoring it, and it would quietly turn governance off for an operator who may simply have forgotten the index. A clear startup error matches how the neighbouring incompatible combinations are handled, such as -prune with -disablegovernance=false. If reviewers prefer the soft-set, it is an equally small change.
  • Unchanged: -prune still soft-sets -disablegovernance/-txindex=0 and keeps its own error for -disablegovernance=false. A masternode with -txindex=0 still gets the existing "Masternode must have transaction index enabled" error first, because the masternode block runs before the new check. The peer scoring in net_governance.cpp is left as is; with the check restored, a node that would score honest peers for valid proposals can no longer start outside regtest.

How Has This Been Tested?

Built on macOS arm64 (--enable-debug --without-gui --enable-werror).

  • New regression test against the unfixed dashd: test/functional/test_runner.py feature_governance_txindex_devnet.py fails. The node does not exit and keeps running with -txindex=0: dashd should have exited within 60s with expected error Error: Transaction index can't be disabled with governance validation enabled. ....
  • The same test with the fix: passes (2-3 s), before and after rebasing onto 8819507.
  • Reproduction Part A with the fix: dashd refused to start (exit 1); stderr: Error: Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index. -txindex=0 -disablegovernance still starts with the warning. Parts B and C are regtest-only and are unaffected by the fix, as intended.
  • Related existing functional tests with the fix, all passed: feature_config_args.py, feature_governance_objects.py, feature_masternode_params_devnet.py, p2p_connect_to_devnet.py, feature_reindex.py, rpc_blockchain.py (v1 and v2 transport), rpc_misc.py, wallet_import_rescan.py --legacy-wallet. feature_unsupported_utxo_db.py was skipped locally (it needs previous-release binaries and Linux).
  • Lint: lint-whitespace.py, lint-python.py, lint-files.py, lint-tests.py, lint-format-strings.py, lint-python-dead-code.py, lint-qt-translation.py are clean. The C++ hunk is formatted with contrib/devtools/clang-format-diff.py.

Breaking Changes

Outside regtest, a node started with -txindex=0 and without -disablegovernance now refuses to start, as it did before v23.1.0. Such a node could not validate proposals anyway. To keep running without the index, operators add -disablegovernance. The same applies to -reindex-chainstate runs, which already require -txindex=0.

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

… is enabled

Governance looks up proposal collateral transactions through the
transaction index, so a node running governance validation with
-txindex=0 rejects every proposal as "Invalid proposal collateral",
and adds misbehavior score to every peer that relays one.

Commit 91129d4 removed the LoadChainstate check that refused this
combination outside regtest, on the assumption that governance is now
force-disabled in this case. Nothing does that: only -prune soft-sets
-disablegovernance, so an explicit -txindex=0 starts a node with
governance enabled and no warning.

Bring the check back in AppInitParameterInteraction with the original
error message and the regtest exemption, and cover it with a devnet
functional test.

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 34 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: 27c56bf4-68c6-4d1d-ad2c-b5313ba5ab7e
📥 Commits

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

📒 Files selected for processing (3)
  • src/init.cpp
  • test/functional/feature_governance_txindex_devnet.py
  • test/functional/test_runner.py
  • 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 d77f357) · triage: low · Phase 2 only (queue backlog)

@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 2 only (queue backlog)

The restored startup check matches the original governance/txindex condition and regtest exemption, preserves existing masternode and pruning checks, and has a registered devnet test covering both rejection and the governance-disabled alternative. The reviewed range contains one coherent commit with no unrelated changes or confirmed in-scope defects. Verification was static only: the supplied CI snapshot shows successful formatting and container preparation checks, two queued manifest jobs, and a PR-title validation failure consistent with the workflow excluding the init scope; independent build and functional-test results are not available in that snapshot.

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: 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) — The diff restores a small, well-contained startup parameter check in src/init.cpp and adds a focused functional regression test without changing governance validation or peer-handling logic.
  • Phase 1 reviewers: not run (skipped for throughput: 20 PRs queued, above the 10 limit)
  • 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

@thepastaclaw thepastaclaw added the pastaclaw:approved thepastaclaw's latest review approved this PR label Oct 4, 2026
@PastaPastaPasta PastaPastaPasta changed the title fix(init): restore the txindex requirement when governance validation is enabled fix: restore the txindex requirement when governance validation is enabled Oct 4, 2026

@knst knst 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.

utACK d77f357

from test_framework.test_framework import BitcoinTestFramework


class GovernanceTxindexDevnetTest(BitcoinTestFramework):

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.

nit: I'd move this scenario to test/functional/p2p_connect_to_devnet.py or test/functional/feature_masternode_params_devnet.py but that's fine for now

@knst knst added this to the 24 milestone Oct 5, 2026
@PastaPastaPasta
PastaPastaPasta merged commit 248a9d9 into dashpay:develop Oct 5, 2026
49 of 50 checks passed
@thepastaclaw thepastaclaw removed the pastaclaw:approved thepastaclaw's latest review approved this PR label Oct 5, 2026
PastaPastaPasta added a commit that referenced this pull request Oct 7, 2026
…idation is enabled

d77f357 fix(init): restore the txindex requirement when governance validation is enabled (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  Governance validation looks up each proposal's collateral transaction through the transaction index. Without `-txindex`, every proposal fails validation with "Invalid proposal collateral". Until v23.1.0 `dashd` refused to start with governance enabled and `-txindex=0` outside regtest:

  > Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.

  Commit 91129d4 ("refactor: drop dead code with ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED", merged in #7008) removed this check from `LoadChainstate`. Its commit message says governance is now force-disabled in this case and the "governance validation disabled" warning is shown instead. That is not the case: only `-prune` soft-sets `-disablegovernance`. An explicit `-txindex=0` on a non-pruned mainnet, testnet or devnet node that is not a masternode now starts quietly with governance enabled and no transaction index. That node then rejects every proposal it is asked to submit or receives from peers, and adds misbehavior score to each peer that relays one.

  ### Why this is a real problem

  Code path at c14104b:

  - The only parameter interaction that turns governance off is the `-prune` one: [src/init.cpp#L1083-L1091](https://github.com/dashpay/dash/blob/c14104b172a2/src/init.cpp#L1083-L1091). The txindex requirement applies only to masternodes ([src/init.cpp#L1398-L1400](https://github.com/dashpay/dash/blob/c14104b172a2/src/init.cpp#L1398-L1400)), the warning fires only when `-disablegovernance` is set ([src/init.cpp#L1442-L1447](https://github.com/dashpay/dash/blob/c14104b172a2/src/init.cpp#L1442-L1447)), and governance is enabled based solely on `-disablegovernance` ([src/init.cpp#L1732](https://github.com/dashpay/dash/blob/c14104b172a2/src/init.cpp#L1732)).
  - `CGovernanceObject::IsCollateralValid` finds the collateral only through `g_txindex` and fails with "Can't find collateral tx" when it is null: [src/governance/object.cpp#L796-L806](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/object.cpp#L796-L806). Proposals then fail `IsValidLocally` ([src/governance/object.cpp#L738-L741](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/object.cpp#L738-L741)), and `ProcessObject` returns false ([src/governance/governance.cpp#L266-L269](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/governance.cpp#L266-L269)).
  - For a proposal received from a peer, a false return from `ProcessObject` adds 20 misbehavior points to that peer: [src/governance/net_governance.cpp#L193-L198](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/net_governance.cpp#L193-L198). After five proposals the peer reaches the discouragement threshold and, unless it is a manual or `noban` connection, is disconnected and its address discouraged: [src/net_processing.cpp#L5833-L5869](https://github.com/dashpay/dash/blob/c14104b172a2/src/net_processing.cpp#L5833-L5869).

  The reproductions below ran against an unfixed `dashd`: Parts A and B against c14104b, Part C against the branch rebased on 8819507 with the `src/init.cpp` hunk reversed (nothing between those two commits touches C++). They are small `BitcoinTestFramework` scripts. Part A uses devnet because regtest is exempt from the check. The framework cannot mine the devnet genesis block, so Parts B and C show the effects on regtest, where `-txindex=0` with governance is allowed on every revision. The collateral lookup and peer scoring are the same on every network.

  <details>
  <summary>Part A (devnet): a node with <code>-txindex=0</code> starts with governance enabled and no warning</summary>

  ```
  $ DASH_WORKTREE=<worktree> python3 repro_a_devnet_startup.py --configfile=<worktree>/test/config.ini
  TestFramework (INFO): Start a devnet node with -txindex=0 (governance not disabled)
  TestFramework (INFO): RESULT: dashd started; stderr: ''
  TestFramework (INFO): getindexinfo: {}
  TestFramework (INFO): debug.log: ... Command-line arg: txindex="0"
  TestFramework (INFO): debug.log: ... [flat-database.h:194] [Store] Verifying governance.dat format...
  TestFramework (INFO): debug.log: ... [flat-database.h:200] [Store] Writing info to governance.dat...
  TestFramework (INFO): debug.log: ... [flat-database.h:71] [CoreWrite]      Governance Objects: 0 (Proposals: 0, Triggers: 0, Other: 0; Erased: 0)
  TestFramework (INFO): debug.log: ... [governance/governance.cpp:1105] [UpdatedBlockTip] [gobject] CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: 0
  TestFramework (INFO): Start a devnet node with -txindex=0 -disablegovernance
  TestFramework (INFO): RESULT: dashd started
  TestFramework (INFO): RESULT: stderr was the governance-disabled warning
  TestFramework (INFO): Tests successful
  ```

  </details>

  <details>
  <summary>Part B (regtest): with governance enabled and <code>-txindex=0</code>, a valid proposal is rejected; the same node accepts it with <code>-txindex=1</code></summary>

  ```
  $ DASH_WORKTREE=<worktree> python3 repro_b_regtest_proposal.py --configfile=<worktree>/test/config.ini
  TestFramework (INFO): Node started with -txindex=0; getindexinfo: {}
  TestFramework (INFO): Collateral 246c979b734f002c8f7d1f472fe159f2a1541472e00fd44b6682134b36327da6 confirmations: 6
  TestFramework (INFO): RESULT (-txindex=0): gobject submit rejected: Governance object is not valid - 6926f935aa79335ddac0cf52ca7665a15dc58cda80c89af9dcd22501a819fe63 - Invalid proposal collateral (-32603)
  TestFramework (INFO): debug.log: ... [governance/object.cpp:804] [IsCollateralValid] CGovernanceObject::IsCollateralValid -- Can't find collateral tx 246c979b734f002c8f7d1f472fe159f2a1541472e00fd44b6682134b36327da6
  TestFramework (INFO): gobject count: {'objects_total': 0, 'proposals': 0, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
  TestFramework (INFO): Restart the same node with -txindex=1 and submit the same proposal
  TestFramework (INFO): RESULT (-txindex=1): gobject submit accepted: 6926f935aa79335ddac0cf52ca7665a15dc58cda80c89af9dcd22501a819fe63
  TestFramework (INFO): gobject count: {'objects_total': 1, 'proposals': 1, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
  TestFramework (INFO): Tests successful
  ```

  </details>

  <details>
  <summary>Part C (regtest): the <code>-txindex=0</code> node scores 20 misbehavior points per valid proposal relayed by a peer and disconnects it after five</summary>

  node0 (`-txindex=1`) connects to node1 (`-txindex=0`, governance enabled), so node1 sees node0 as an ordinary inbound peer. node0 then submits five valid proposals. Because node0 is on a local address, node1 disconnects it without adding the address to the discouragement list; a non-local peer is disconnected and discouraged.

  ```
  $ DASH_WORKTREE=<worktree> python3 repro_c_peer_misbehaving.py --configfile=<worktree>/test/config.ini
  TestFramework (INFO): node0 getindexinfo: {'txindex': {'synced': True, 'best_block_height': 0}}
  TestFramework (INFO): node1 getindexinfo: {}
  TestFramework (INFO): node1 sees node0 as peer=0 inbound=True connection_type=inbound permissions=[]
  TestFramework (INFO): node0 accepted proposal b23bc414c1e0b465070af792c669ab4eabc529f2deee560ef4593458f01a499e
  TestFramework (INFO): node0 accepted proposal c81ae2dd9fe14ac2c13eea80ad52a87df586e7be6e317a6540b240a940678ab4
  TestFramework (INFO): node0 accepted proposal e6a890d8da141a791fb5cb5b4dfbde92da3e423f7258b383cc3f8e9fa1620c7b
  TestFramework (INFO): node0 accepted proposal 9e7706395cfacfdbb9aad6e3762abece051a60e5db0ebbec219a9e1201284c54
  TestFramework (INFO): node0 accepted proposal 499abad8125b978ba13ec4b6e19bb2bab9d9d63a5475f6e8c37bbc739e8780e3
  TestFramework (INFO): node0 gobject count: {'objects_total': 5, 'proposals': 5, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
  TestFramework (INFO): node1 gobject count: {'objects_total': 0, 'proposals': 0, 'triggers': 0, 'other': 0, 'erased': 0, 'votes': 0}
  TestFramework (INFO): node1 peers after relay: 0
  TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (0 -> 20)
  TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (20 -> 40)
  TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (40 -> 60)
  TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (60 -> 80)
  TestFramework (INFO): node1 debug.log: ... [governance/governance.cpp:268] [ProcessObject] [gobject] MNGOVERNANCEOBJECT -- Governance object is invalid - Invalid proposal collateral
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:1954] [Misbehaving] [net] Misbehaving: peer=0 (80 -> 100) DISCOURAGE THRESHOLD EXCEEDED
  TestFramework (INFO): node1 debug.log: ... [net_processing.cpp:5860] [MaybeDiscourageAndDisconnect] [net] Warning: disconnecting but not discouraging local peer 0!
  TestFramework (INFO): Tests successful
  ```

  </details>

  ### Why it matters

  This only affects operators who explicitly set `-txindex=0` (the default is `1`) on a non-pruned node that is not a masternode. Nothing at startup tells them anything is wrong, and:

  - `gobject list`/`count`, the governance view in the GUI and proposal vote tracking stay empty or incomplete, and `gobject submit` fails with an unhelpful collateral error.
  - The node does not relay proposals.
  - Every honest peer that relays five or more proposals to it, which on mainnet includes any peer it syncs governance objects from, reaches the discouragement threshold and is disconnected; non-local peers are also discouraged. This applies to its outbound peers as well as inbound ones, so the misconfigured node keeps losing its own connections.

  Trigger and superblock validation do not use the transaction index, so block validation is not affected.

  The regression was introduced in v23.1.0. It is not a v24.0.0 release-candidate regression.

  ## What was done?

  Restored the check in `AppInitParameterInteraction`, next to the other governance and masternode parameter checks. Outside regtest, if governance validation is enabled and `-txindex` is disabled, `dashd` now fails to start with the original error message.

  Added `feature_governance_txindex_devnet.py`. It runs on devnet, because regtest is exempt, and checks two things: `-txindex=0` alone is refused with that error, and `-txindex=0 -disablegovernance` still starts with the governance-disabled warning.

  ### Why this is the correct minimal fix

  - It brings back the behavior from before 91129d4: the same condition, the same translatable message and the same regtest exemption. It is a parameter check, so it lives with the other parameter checks instead of going back into `LoadChainstate`. That needs no changes to the `LoadChainstate` signature or to `ChainstateLoadingError`, and it fails before the block index is loaded.
  - The regtest exemption is kept on purpose. Many regtest functional tests run with `-txindex=0` and governance enabled (`feature_reindex.py`, `rpc_blockchain.py`, `rpc_misc.py`, `feature_unsupported_utxo_db.py`), as they did before the check was removed.
  - The alternative is to do what the 91129d4 commit message describes: soft-set `-disablegovernance` when `-txindex=0`. That would change behavior instead of restoring it, and it would quietly turn governance off for an operator who may simply have forgotten the index. A clear startup error matches how the neighbouring incompatible combinations are handled, such as `-prune` with `-disablegovernance=false`. If reviewers prefer the soft-set, it is an equally small change.
  - Unchanged: `-prune` still soft-sets `-disablegovernance`/`-txindex=0` and keeps its own error for `-disablegovernance=false`. A masternode with `-txindex=0` still gets the existing "Masternode must have transaction index enabled" error first, because the masternode block runs before the new check. The peer scoring in `net_governance.cpp` is left as is; with the check restored, a node that would score honest peers for valid proposals can no longer start outside regtest.

  ## How Has This Been Tested?

  Built on macOS arm64 (`--enable-debug --without-gui --enable-werror`).

  - New regression test against the unfixed `dashd`: `test/functional/test_runner.py feature_governance_txindex_devnet.py` fails. The node does not exit and keeps running with `-txindex=0`: `dashd should have exited within 60s with expected error Error: Transaction index can't be disabled with governance validation enabled. ...`.
  - The same test with the fix: passes (2-3 s), before and after rebasing onto 8819507.
  - Reproduction Part A with the fix: `dashd refused to start (exit 1); stderr: Error: Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.` `-txindex=0 -disablegovernance` still starts with the warning. Parts B and C are regtest-only and are unaffected by the fix, as intended.
  - Related existing functional tests with the fix, all passed: `feature_config_args.py`, `feature_governance_objects.py`, `feature_masternode_params_devnet.py`, `p2p_connect_to_devnet.py`, `feature_reindex.py`, `rpc_blockchain.py` (v1 and v2 transport), `rpc_misc.py`, `wallet_import_rescan.py --legacy-wallet`. `feature_unsupported_utxo_db.py` was skipped locally (it needs previous-release binaries and Linux).
  - Lint: `lint-whitespace.py`, `lint-python.py`, `lint-files.py`, `lint-tests.py`, `lint-format-strings.py`, `lint-python-dead-code.py`, `lint-qt-translation.py` are clean. The C++ hunk is formatted with `contrib/devtools/clang-format-diff.py`.

  ## Breaking Changes

  Outside regtest, a node started with `-txindex=0` and without `-disablegovernance` now refuses to start, as it did before v23.1.0. Such a node could not validate proposals anyway. To keep running without the index, operators add `-disablegovernance`. The same applies to `-reindex-chainstate` runs, which already require `-txindex=0`.

  ## 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)

ACKs for top commit:
  knst:
    utACK d77f357

Tree-SHA512: 55ca460164adbe6e77ec7ba926e9d07516c20ee0162a86c2d98551ca47712090e03b08948929f40c7221ab9741b4ce8a6369b80a6800e4526f0efeaad147443c
(cherry picked from commit 248a9d9)
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