Skip to content

fix(net): request object votes from nPeersPerHashMax peers - #7791

Merged
PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/request-object-votes-from-npeersperhashmax-peers
Oct 4, 2026
Merged

PastaPastaPasta merged 1 commit into
dashpay:developfrom
PastaPastaPasta:fix/request-object-votes-from-npeersperhashmax-peers

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

SyncManager::RequestGovernanceObjectVotes is meant to ask up to nPeersPerHashMax distinct peers (3 on real networks, unlimited on regtest) for the votes of each governance object, at most once per peer per hour. When the per-object filter was moved into CGovernanceManager::FetchGovernanceObjectVotes (77faacc, first released in v23.1.0), the caller started passing nMaxObjRequestsPerNode as the helper's nPeersPerHashMax argument. The two values are unrelated: one is how many objects to request from a single peer per call, the other is how many peers to ask about a single object.

On mainnet nMaxObjRequestsPerNode is hard-coded to 1. So after one peer has been asked for an object's votes, the object is dropped from the candidate list for every other peer until the 1 hour mapAskedRecently entry expires. During initial governance sync each object's votes are therefore requested from exactly one peer instead of up to three. On testnet and devnets the bound becomes 2000 / enabled masternodes, which can be larger than 3. Objects that already have 3 asked peers are then re-selected, and that only costs some extra requests.

Why this is a real problem

Code path at c14104b:

Before 77faacc the inline loop compared against nPeersPerHashMax. The refactor passed the other variable.

To show the behaviour directly, I injected a temporary, uncommitted test case into src/test/governance_inv_tests.cpp. It uses the existing mainnet GovernanceInvSetup fixture, adds one governance object and four connected peers, and calls the real SyncManager::RequestGovernanceObjectVotes once per peer, the same way the initial-sync loop does. It then counts the MNGOVERNANCESYNC per-object vote requests queued to each peer. The expected result is one request each to peers 1 to 3 and none to peer 4.

Unfixed (c14104b): only the first peer is ever asked
$ src/test/test_dash --run_test=governance_inv_tests/object_votes_requested_from_up_to_three_peers --log_level=message
Running 1 test case...
peer=1 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
peer=2 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0
test/governance_inv_tests.cpp:599: error: in "governance_inv_tests/object_votes_requested_from_up_to_three_peers": peer=2 was asked for the object's votes 0 time(s), expected 1
peer=3 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0
test/governance_inv_tests.cpp:599: error: in "governance_inv_tests/object_votes_requested_from_up_to_three_peers": peer=3 was asked for the object's votes 0 time(s), expected 1
peer=4 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0

*** 2 failures are detected in the test module "Dash Core Test Suite"
exit=201
With this PR: three peers are asked, the fourth is not
$ src/test/test_dash --run_test=governance_inv_tests/object_votes_requested_from_up_to_three_peers --log_level=message
Running 1 test case...
peer=1 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
peer=2 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
peer=3 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
peer=4 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0

*** No errors detected
exit=0

Why it matters

On mainnet the vote sync for each governance object has no redundancy. If the single peer picked for an object is slow, lagging or does not answer, the node's vote tally for that object stays incomplete. It stays that way until the hourly re-request after sync, or until the missing votes arrive through normal inventory relay. This affects nodes doing initial governance sync, such as fresh nodes and nodes restarting after a long sleep. There is no consensus or funds impact. The effect is slower and less reliable convergence of governance vote tallies. The regression has shipped since v23.1.0.

What was done?

Pass nPeersPerHashMax instead of nMaxObjRequestsPerNode as the first argument of FetchGovernanceObjectVotes in SyncManager::RequestGovernanceObjectVotes.

Why this is the correct minimal fix

The callee's parameter is the per-object peer cap, and the caller already computes the correct value (nPeersPerHashMax) and uses it for the matching inner-loop break. Passing it restores the pre-77faacce semantics exactly: an object stays a candidate until 3 distinct peers (unlimited on regtest) have been asked within the last hour. nMaxObjRequestsPerNode keeps its own role as the bound of the per-call object loop, so the number of objects requested from each peer per call does not change. No other caller of FetchGovernanceObjectVotes exists. A strong type for the argument, or a rename, would stop this kind of mix-up in the future, but both are refactors beyond restoring the intended behaviour.

How Has This Been Tested?

  • Reproduction above: a temporary test case in src/test/governance_inv_tests.cpp that calls SyncManager::RequestGovernanceObjectVotes per peer on the mainnet fixture. It fails on the unfixed tree (peers 2 and 3 never asked) and passes with this change (peers 1 to 3 asked once, peer 4 not asked). It is not part of this PR. RequestGovernanceObjectVotes is a private member, and the harness reached it with an explicit-instantiation access trick, which is not acceptable in a committed test. The regtest functional tests cannot observe the bug, because on mockable chains the cap is effectively unlimited both before and after the fix (2000 / MN count vs SIZE_MAX).
  • Unit tests, all passing: src/test/test_dash --run_test=<suite> for governance_inv_tests, governance_vote_sync_tests, governance_vote_processing_tests, governance_vote_wire_tests, governance_superblock_tests, governance_validators_tests.
  • Functional tests, all passing: test/functional/test_runner.py feature_governance.py feature_governance_cl.py feature_governance_objects.py p2p_govsync_bloom.py. In the first parallel run, feature_governance.py hit an RPC timeout while the host load average was about 100. It passed when rerun on its own.
  • clang-format on the changed lines produces no changes; test/lint/lint-whitespace.py passes.

Environment: macOS arm64, debug build (--enable-debug --enable-werror).

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

When the per-object filter in RequestGovernanceObjectVotes was moved into
CGovernanceManager::FetchGovernanceObjectVotes, the caller started passing
nMaxObjRequestsPerNode as the helper's nPeersPerHashMax argument. On
mainnet nMaxObjRequestsPerNode is 1, so once a single peer had been asked
for an object's votes the object was filtered out for every other peer
for the next hour, instead of being requested from up to 3 peers. On
other networks the bound became 2000 / enabled masternodes, which can
exceed 3.

Pass nPeersPerHashMax again, as the inline loop did before the move.

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: 7538972e-5cda-4fd4-8033-7dc4f2b0431c
📥 Commits

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

📒 Files selected for processing (1)
  • src/node/sync_manager.cpp
  • 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 — 1 blocking finding(s) (commit aaa5301) · triage: low

@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

The one-line change at aaa5301 correctly restores the per-object peer cap while preserving the independent object-request budget and hourly expiration; no source-code defect was found. The PR title is rejected by the title-validation workflow and needs a metadata-only correction. This was a static review: current CI shows successful formatting, merge checks, and amd64/arm64 container builds, pending multi-architecture manifests, failed title validation, and an unsuccessful predict_conflicts check with no failed-job log returned.

🔴 1 blocking

1 finding(s) not shown inline (the lines are not part of this PR's diff)

🔴 Blocking: PR title uses a scope rejected by title validation
.github/workflows/semantic-pull-request.yml:39-52

The current PR title, fix(governance): request object votes from nPeersPerHashMax peers, uses governance, which is absent from the workflow's allowed scopes and the matching list in CONTRIBUTING.md. The failed log for run 37182444330 explicitly reports Unknown scope "governance", and the current PR check still fails at this exact head. Rename the PR to fix(net): request object votes from nPeersPerHashMax peers to use the supported P2P-network scope. This requires only a PR-title edit, not changes to the workflow or source code.

source: glm-5.3-flash (phase1-reviewer: general)

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) — The diff corrects a single argument in SyncManager::RequestGovernanceObjectVotes, a small and well-contained behavioral fix whose correctness is straightforward to verify against the callee's per-object peer limit.
  • 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
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `.github/workflows/semantic-pull-request.yml`:
- [BLOCKING] .github/workflows/semantic-pull-request.yml:39-52: PR title uses a scope rejected by title validation
  The current PR title, `fix(governance): request object votes from nPeersPerHashMax peers`, uses `governance`, which is absent from the workflow's allowed scopes and the matching list in CONTRIBUTING.md. The failed log for run 37182444330 explicitly reports `Unknown scope "governance"`, and the current PR check still fails at this exact head. Rename the PR to `fix(net): request object votes from nPeersPerHashMax peers` to use the supported P2P-network scope. This requires only a PR-title edit, not changes to the workflow or source code.

@thepastaclaw thepastaclaw added the pastaclaw:changes-requested thepastaclaw's latest review requested changes label Oct 4, 2026
@PastaPastaPasta PastaPastaPasta changed the title fix(governance): request object votes from nPeersPerHashMax peers fix(net): request object votes from nPeersPerHashMax peers Oct 4, 2026
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Re the PR-title finding: the title is now fix(net): request object votes from nPeersPerHashMax peers, which uses an allowed scope. Validate PR title passes at the same head, and no code changed.


🤖 Posted autonomously by Claude on behalf of pasta.

@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 a8b44a1 into dashpay:develop Oct 4, 2026
48 of 50 checks passed
@thepastaclaw thepastaclaw removed the pastaclaw:changes-requested thepastaclaw's latest review requested changes 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
aaa5301 fix(governance): request object votes from nPeersPerHashMax peers (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  `SyncManager::RequestGovernanceObjectVotes` is meant to ask up to `nPeersPerHashMax` distinct peers (3 on real networks, unlimited on regtest) for the votes of each governance object, at most once per peer per hour. When the per-object filter was moved into `CGovernanceManager::FetchGovernanceObjectVotes` (77faacc, first released in v23.1.0), the caller started passing `nMaxObjRequestsPerNode` as the helper's `nPeersPerHashMax` argument. The two values are unrelated: one is how many objects to request from a single peer per call, the other is how many peers to ask about a single object.

  On mainnet `nMaxObjRequestsPerNode` is hard-coded to 1. So after one peer has been asked for an object's votes, the object is dropped from the candidate list for every other peer until the 1 hour `mapAskedRecently` entry expires. During initial governance sync each object's votes are therefore requested from exactly one peer instead of up to three. On testnet and devnets the bound becomes `2000 / enabled masternodes`, which can be larger than 3. Objects that already have 3 asked peers are then re-selected, and that only costs some extra requests.

  ### Why this is a real problem

  Code path at c14104b:

  - [`src/node/sync_manager.cpp#L55`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L55) and [`#L66`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L66): `nPeersPerHashMax` is 3 on real networks.
  - [`src/node/sync_manager.cpp#L74`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L74): `nMaxObjRequestsPerNode` is 1 on mainnet.
  - [`src/node/sync_manager.cpp#L86`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L86): `FetchGovernanceObjectVotes(nMaxObjRequestsPerNode, nNow, mapAskedRecently)`.
  - [`src/governance/governance.cpp#L942`](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/governance.cpp#L942) and [`#L954`](https://github.com/dashpay/dash/blob/c14104b172a2/src/governance/governance.cpp#L954): the parameter is named `nPeersPerHashMax`, and an object is skipped when `asked_peers.size() >= nPeersPerHashMax`.
  - [`src/node/sync_manager.cpp#L122`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L122): the inner loop still stops at `nPeersPerHashMax`, but it never gets the chance on mainnet, because the outer filter has already removed the object.
  - [`src/node/sync_manager.cpp#L297-L298`](https://github.com/dashpay/dash/blob/c14104b172a2/src/node/sync_manager.cpp#L297-L298): initial sync calls `RequestGovernanceObjectVotes` with one peer at a time, so the per-object filter is what decides whether a second and third peer are asked.

  Before 77faacc the inline loop compared against `nPeersPerHashMax`. The refactor passed the other variable.

  To show the behaviour directly, I injected a temporary, uncommitted test case into `src/test/governance_inv_tests.cpp`. It uses the existing mainnet `GovernanceInvSetup` fixture, adds one governance object and four connected peers, and calls the real `SyncManager::RequestGovernanceObjectVotes` once per peer, the same way the initial-sync loop does. It then counts the `MNGOVERNANCESYNC` per-object vote requests queued to each peer. The expected result is one request each to peers 1 to 3 and none to peer 4.

  <details><summary>Unfixed (c14104b): only the first peer is ever asked</summary>

  ```
  $ src/test/test_dash --run_test=governance_inv_tests/object_votes_requested_from_up_to_three_peers --log_level=message
  Running 1 test case...
  peer=1 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
  peer=2 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0
  test/governance_inv_tests.cpp:599: error: in "governance_inv_tests/object_votes_requested_from_up_to_three_peers": peer=2 was asked for the object's votes 0 time(s), expected 1
  peer=3 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0
  test/governance_inv_tests.cpp:599: error: in "governance_inv_tests/object_votes_requested_from_up_to_three_peers": peer=3 was asked for the object's votes 0 time(s), expected 1
  peer=4 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0

  *** 2 failures are detected in the test module "Dash Core Test Suite"
  exit=201
  ```

  </details>

  <details><summary>With this PR: three peers are asked, the fourth is not</summary>

  ```
  $ src/test/test_dash --run_test=governance_inv_tests/object_votes_requested_from_up_to_three_peers --log_level=message
  Running 1 test case...
  peer=1 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
  peer=2 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
  peer=3 RequestGovernanceObjectVotes returned 0, MNGOVERNANCESYNC (per-object vote request) messages queued=1
  peer=4 RequestGovernanceObjectVotes returned -2, MNGOVERNANCESYNC (per-object vote request) messages queued=0

  *** No errors detected
  exit=0
  ```

  </details>

  ### Why it matters

  On mainnet the vote sync for each governance object has no redundancy. If the single peer picked for an object is slow, lagging or does not answer, the node's vote tally for that object stays incomplete. It stays that way until the hourly re-request after sync, or until the missing votes arrive through normal inventory relay. This affects nodes doing initial governance sync, such as fresh nodes and nodes restarting after a long sleep. There is no consensus or funds impact. The effect is slower and less reliable convergence of governance vote tallies. The regression has shipped since v23.1.0.

  ## What was done?

  Pass `nPeersPerHashMax` instead of `nMaxObjRequestsPerNode` as the first argument of `FetchGovernanceObjectVotes` in `SyncManager::RequestGovernanceObjectVotes`.

  ### Why this is the correct minimal fix

  The callee's parameter is the per-object peer cap, and the caller already computes the correct value (`nPeersPerHashMax`) and uses it for the matching inner-loop break. Passing it restores the pre-77faacce semantics exactly: an object stays a candidate until 3 distinct peers (unlimited on regtest) have been asked within the last hour. `nMaxObjRequestsPerNode` keeps its own role as the bound of the per-call object loop, so the number of objects requested from each peer per call does not change. No other caller of `FetchGovernanceObjectVotes` exists. A strong type for the argument, or a rename, would stop this kind of mix-up in the future, but both are refactors beyond restoring the intended behaviour.

  ## How Has This Been Tested?

  - Reproduction above: a temporary test case in `src/test/governance_inv_tests.cpp` that calls `SyncManager::RequestGovernanceObjectVotes` per peer on the mainnet fixture. It fails on the unfixed tree (peers 2 and 3 never asked) and passes with this change (peers 1 to 3 asked once, peer 4 not asked). It is not part of this PR. `RequestGovernanceObjectVotes` is a private member, and the harness reached it with an explicit-instantiation access trick, which is not acceptable in a committed test. The regtest functional tests cannot observe the bug, because on mockable chains the cap is effectively unlimited both before and after the fix (2000 / MN count vs `SIZE_MAX`).
  - Unit tests, all passing: `src/test/test_dash --run_test=<suite>` for `governance_inv_tests`, `governance_vote_sync_tests`, `governance_vote_processing_tests`, `governance_vote_wire_tests`, `governance_superblock_tests`, `governance_validators_tests`.
  - Functional tests, all passing: `test/functional/test_runner.py feature_governance.py feature_governance_cl.py feature_governance_objects.py p2p_govsync_bloom.py`. In the first parallel run, `feature_governance.py` hit an RPC timeout while the host load average was about 100. It passed when rerun on its own.
  - `clang-format` on the changed lines produces no changes; `test/lint/lint-whitespace.py` passes.

  Environment: macOS arm64, debug build (`--enable-debug --enable-werror`).

  ## 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: eb194251f30e68eb8bcd0ed647d48b086337c32e02fae968bbb057083cd5e8d4ac8012b6d11de441a7511d78b91f34dc2f299a7ebee35bfc4696770fe4744013
(cherry picked from commit a8b44a1)
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