Repository navigation
fix: update masternode rate limit on failed governance trigger path - #7521
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review. WalkthroughGovernance trigger handling now records trigger creation times in the masternode rate buffer before registration. Rejected triggers are marked for deletion and are not relayed. Retained triggers can be scheduled for deferred relay when their creation time falls within the future-time reliability window. New tests check rate-buffer use and verify that failed triggers are not relayed immediately or after aging. Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to No new merge-blocking risk is established for this change. The existing rate-limiter concern remains separately documented. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change strengthens failed-trigger throttling without adding a network entrypoint or announcing triggers rejected during registration. No introduced security regression was identified. Residual uncertainty concerns interruption and restart behavior, which the added tests do not exercise. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
✅ Final review complete — no blockers (commit a551177) · triage: low |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
The final code correctly advances the masternode rate buffer for failed triggers while deferring relay scheduling until after the trigger is retained, and the tests cover both behaviors. No correctness blockers remain, but two same-stack fixup commits should be folded into their originating commits to keep the history atomic and avoid preserving a known regression.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— final-verifier (fallback) - Sonnet reviewers:
claude-sonnet-5— general (failed),claude-sonnet-5— dash-core-commit-history (completed),claude-sonnet-5— general (completed)
🟡 2 suggestion(s)
2 additional finding(s) omitted (not in diff).
🤖 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 `<commit:217a4097042>`:
- [SUGGESTION] <commit:217a4097042>:1: Squash the relay self-correction into the original fix
Commit 8862d235bb6 moves MasternodeRateUpdate() above the AddTrigger check while that function still inserts near-future triggers into setAdditionalRelayObjects. This creates a new intermediate regression where a malformed trigger marked for deletion can later be re-announced and served on GETDATA. Commit 217a4097042 correctly separates rate accounting from relay scheduling and adds targeted coverage, but it explicitly repairs behavior introduced by the preceding commit in this PR. Fold its governance and regression-test changes into 8862d235bb6 so the rate-limit fix is atomic and no retained commit contains the known relay-amplification regression.
In `<commit:39ba2d8ba97>`:
- [SUGGESTION] <commit:39ba2d8ba97>:1: Fold the audit-ID cleanup into the originating commits
Commit 39ba2d8ba97 only removes private audit identifiers from two comments introduced earlier in this same stack, making it a fixup commit under CONTRIBUTING.md's definition of commits that repeatedly change the same lines. It also leaves U009/U003 in the permanent messages of 8862d235bb6 and 217a4097042, so the stated cleanup remains incomplete. Amend the originating comments and commit messages to omit the identifiers, then drop this standalone cleanup commit.
39ba2d8 to
7c7bda9
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
The final tree correctly advances the masternode rate buffer for failed triggers while scheduling deferred relay only after a trigger is retained, and the tests cover both paths. Carried forward from the prior review, two commit-history suggestions remain unresolved: the same-PR relay self-correction should be folded into the original fix, and the audit-ID cleanup should be folded into the commits that introduced those references. The latest delta is a pure rebase with identical PR patches, so no new defects or findings were introduced.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— final-verifier (fallback) - Sonnet reviewers:
claude-sonnet-5— general (failed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— general (completed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (completed)
🟡 2 suggestion(s)
2 additional finding(s) omitted (not in diff).
🤖 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 `<commit:6a36c89a4a6>`:
- [SUGGESTION] <commit:6a36c89a4a6>:1: Squash the relay self-correction into the original fix
Commit 643ba6d2068 moves MasternodeRateUpdate() above the AddTrigger check while deferred-relay scheduling is still a side effect of that function. At this intermediate commit, a malformed trigger rejected by AddTrigger and marked for deletion can still be queued in setAdditionalRelayObjects, later re-announced, and served on GETDATA. Commit 6a36c89a4a6 correctly extracts ScheduleAdditionalRelay() and calls it only after AddTrigger succeeds, but this repairs a regression introduced by the immediately preceding commit in the same PR. Fold the production correction and its relay regression coverage into the original fix so every retained commit contains the complete rate-limit fix without the known relay-amplification path.
In `<commit:7c7bda98220>`:
- [SUGGESTION] <commit:7c7bda98220>:1: Fold the audit-ID cleanup into the originating commits
Commit 7c7bda98220 only removes private U009/U003 identifiers from two comments introduced earlier in this same stack, making it an intra-stack fixup rather than an independent documentation change. The identifiers also remain in the permanent messages of commits 643ba6d2068 and 6a36c89a4a6. Amend the originating comments and commit messages to omit the private identifiers, then drop the standalone cleanup commit so the cleanup is complete and the history remains atomic.
7c7bda9 to
1d6dda8
Compare
PastaPastaPasta
left a comment
There was a problem hiding this comment.
LGTM; but this one is a bit interesting enough I want another's review
|
PR #7536 adds shared |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
Independently verified: PR #7521 is now a single commit (1d6dda8) sitting directly on develop, containing the rate-buffer-update reordering, the ScheduleAdditionalRelay extraction gated to run only after AddTrigger succeeds, and both regression tests. The diff between the previously reviewed head (7c7bda9) and the current head is empty, confirming this is a history-only rewrite with no source changes. Both prior commit-hygiene findings are fixed: the self-correction is folded atomically into the fix, and no audit-ID identifiers remain in code, tests, or the commit message.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
claude-sonnet-5— final-verifier - Sonnet reviewers:
claude-sonnet-5— general (failed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— general (completed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (completed)
1d6dda8 to
4e76c10
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e76c10a5d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
This pull request has conflicts, please rebase. |
PastaPastaPasta
left a comment
There was a problem hiding this comment.
LGTM; but this one is a bit interesting enough I want another's review
knst
left a comment
There was a problem hiding this comment.
implementation is fine and regressions test idea is fine.
Though, impplementation of regressions tests requires re-working
|
Addressed @knst's review in a03a5e6:
Kept a separate Verified:
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/test/governance_inv_tests.cpp (1)
628-631: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive the near-future offset from the named constants.
3550encodes the current values ofMAX_TIME_FUTURE_DEVIATIONandRELIABLE_PROPAGATION_TIME. If either constant changes,near_futuremay fall out of theScheduleTriggerRelay()arming window, and this test stops covering the deferred-relay regression. Compute the offset from the constants, and make thenow + 120readiness step depend onRELIABLE_PROPAGATION_TIMEas well.♻️ Proposed change to derive the offsets
- const int64_t near_future = now + 3550; + // Halfway inside the arming window so the test stays valid if either constant changes. + const int64_t near_future = now + count_seconds(MAX_TIME_FUTURE_DEVIATION) - + count_seconds(RELIABLE_PROPAGATION_TIME) / 2;- SetMockTime(std::chrono::seconds{now + 120}); + SetMockTime(std::chrono::seconds{now} + RELIABLE_PROPAGATION_TIME + 1s);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/governance_inv_tests.cpp` around lines 628 - 631, Update the near_future setup in the governance inventory test to derive its offset from MAX_TIME_FUTURE_DEVIATION and RELIABLE_PROPAGATION_TIME instead of the hard-coded 3550, keeping it inside the ScheduleTriggerRelay() arming window. Also replace the fixed now + 120 readiness step with an offset derived from RELIABLE_PROPAGATION_TIME so both timing assumptions track the named constants.src/governance/governance.cpp (1)
724-725: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueUse
GetAdjustedTime()for the addable-relay threshold.
ScheduleTriggerRelayarming usesGetTime(), butCheckPostponedObjectschecks validity and readiness forsetAdditionalRelayObjectswithGetAdjustedTime(). When the network offset is non-zero, the extra-relay decision can disagree with the drained readiness decision; useGetAdjustedTime()here to keep the same clock path.♻️ Proposed change
- if (govobj.GetCreationTime() > - GetTime() + count_seconds(MAX_TIME_FUTURE_DEVIATION) - count_seconds(RELIABLE_PROPAGATION_TIME)) { + const auto now{std::chrono::time_point_cast<std::chrono::seconds>(GetAdjustedTime())}; + if (govobj.CreationTime() > now + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/governance/governance.cpp` around lines 724 - 725, The future deviation check in CheckPostponedObjects uses GetTime() for the addable-relay threshold, but elsewhere in the same method GetAdjustedTime() is used for validity and readiness checks. When network offset is non-zero, this clock inconsistency causes the relay decision to disagree with the readiness decision. Replace GetTime() with GetAdjustedTime() in the govobj.GetCreationTime() comparison to ensure both relay-relay and readiness logic follow the same clock path.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/governance/governance.cpp`:
- Around line 724-725: The future deviation check in CheckPostponedObjects uses
GetTime() for the addable-relay threshold, but elsewhere in the same method
GetAdjustedTime() is used for validity and readiness checks. When network offset
is non-zero, this clock inconsistency causes the relay decision to disagree with
the readiness decision. Replace GetTime() with GetAdjustedTime() in the
govobj.GetCreationTime() comparison to ensure both relay-relay and readiness
logic follow the same clock path.
In `@src/test/governance_inv_tests.cpp`:
- Around line 628-631: Update the near_future setup in the governance inventory
test to derive its offset from MAX_TIME_FUTURE_DEVIATION and
RELIABLE_PROPAGATION_TIME instead of the hard-coded 3550, keeping it inside the
ScheduleTriggerRelay() arming window. Also replace the fixed now + 120 readiness
step with an offset derived from RELIABLE_PROPAGATION_TIME so both timing
assumptions track the named constants.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 130bcbdd-d48b-46cf-ad6b-892c23b4710f
📒 Files selected for processing (3)
src/governance/governance.cppsrc/governance/governance.hsrc/test/governance_inv_tests.cpp
|
Follow-up for suite naming / single-setup feedback in 5b4cc0f:
Verified: |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/test/governance_inv_tests.cpp (1)
116-117: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueClarify the fixture teardown after leaving mock time set.
SetMockTime(0s)clears the mock clock globally; this comment does not match that effect. The tests set per-case mock time instead. Reset to0sfrom the destructor or remove the misleading fixture-level comment.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/governance_inv_tests.cpp` around lines 116 - 117, Update the test fixture around SetMockTime(0s) so teardown resets the global mock clock from its destructor, or remove the misleading fixture-level comment if teardown already performs the reset; keep per-case mock-time setup unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/test/governance_inv_tests.cpp`:
- Around line 116-117: Update the test fixture around SetMockTime(0s) so
teardown resets the global mock clock from its destructor, or remove the
misleading fixture-level comment if teardown already performs the reset; keep
per-case mock-time setup unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a1750e5a-7996-4083-a83c-7361dcf84048
📒 Files selected for processing (1)
src/test/governance_inv_tests.cpp
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The production reordering correctly advances the failed-trigger rate buffer, relay scheduling remains gated until after AddTrigger succeeds, and both carried-forward findings remain fixed. However, failed attempts are still accounted using signer-controlled creation timestamps, and the exact rate math permits an operator to submit malformed triggers indefinitely on default testnet, devnet, and regtest by alternating timestamps across the accepted window. The two review-driven test follow-up commits should also be squashed into the originating fix, but that history issue is non-blocking.
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking | 🟡 1 suggestion(s)
1 additional finding(s) omitted (not in diff).
🤖 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 `src/governance/governance.cpp`:
- [BLOCKING] src/governance/governance.cpp:711: Throttle failed triggers using non-attacker-controlled time
The failed-trigger limiter records the operator-supplied creation timestamp, so the PR's flood protection remains bypassable on short-cycle networks. CRateCheckBuffer::GetRate() returns 5 / (max_timestamp - min_timestamp), while MasternodeRateCheck() accepts timestamps from now - 2 * cycle through now + 3600 and permits rates below 2.2 / cycle. On default testnet and devnet, cycle = 24 * 150 = 3600 seconds, making the accepted span 3 cycles and the endpoint-spaced rate 5 / (3 * cycle), which is below 2.2 / cycle. On regtest, cycle = 20 * 150 = 3000 seconds and the accepted span is 3.2 cycles, which also passes. By alternating unique, signature-valid malformed triggers between the accepted endpoints, every rolling five-entry buffer contains both endpoints, so every submission reaches BLS verification, AddTrigger, and mapObjects insertion indefinitely. Mainnet's roughly 2.001-cycle window is too narrow for this bypass, but this implementation and its stated protection are network-generic. Account failed attempts using receipt time or another non-signer-controlled value, and add an endpoint-spaced regression test rather than testing only consecutive creation timestamps.
In `<commit:a03a5e68893>`:
- [SUGGESTION] <commit:a03a5e68893>:1: Squash the review-driven test follow-ups into the fix
Commits a03a5e68893 and 5b4cc0f9d1d are fixups to code introduced by 4e76c10a5da in the same unshipped stack. The first follow-up immediately deletes the standalone test file and Makefile entry created by the fix, relocates the same regressions, and renames the newly introduced helper; the second immediately rewrites that relocation to remove the extra fixture and suite. CONTRIBUTING.md specifically identifies commits that repeatedly change the same code as fixup commits that should be squashed. Fold both follow-ups into 4e76c10a5da so the final helper name, test location, and unified fixture land atomically without permanent blame churn.
|
This pull request has conflicts, please rebase. |
5b4cc0f to
7f3a826
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/governance_inv_tests.cpp (1)
534-536: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExercise the invalid-signature path for a registered masternode.
Line 534 is incorrect.
GovernanceInvSetupregistersmn_outpointat lines 67-77.MakeGovernanceVote()uses a different, unknown outpoint. This test passes on the unknown-masternode rejection path. It does not prove that an orphan vote frommn_outpointwith an invalid signature is rejected before caching.Proposed test input
- // The tip masternode list is empty in this setup, so no vote can name a known collateral. - const CGovernanceVote vote{MakeGovernanceVote(uint256S("51"))}; + CGovernanceVote vote{mn_outpoint, uint256S("51"), VOTE_SIGNAL_FUNDING, VOTE_OUTCOME_YES}; + vote.SetTime(GetTime<std::chrono::seconds>().count()); + vote.SetSignature(std::vector<unsigned char>(CGovernanceVote::COMPACT_SIG_SIZE));Based on PR objectives: orphan votes must prove masternode authorship before caching.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/governance_inv_tests.cpp` around lines 534 - 536, Update the vote construction in the governance inventory test to use the registered mn_outpoint from GovernanceInvSetup while retaining an invalid signature. Ensure the resulting orphan vote reaches invalid-signature validation for the known masternode before caching, rather than the unknown-masternode rejection path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/test/governance_inv_tests.cpp`:
- Around line 584-633: Update failed_trigger_path_advances_masternode_rate_limit
to assert that exactly RATE_BUFFER_SIZE failed triggers are stored, replacing
the permissive upper-bound check while retaining the total-statistics
consistency checks.
---
Outside diff comments:
In `@src/test/governance_inv_tests.cpp`:
- Around line 534-536: Update the vote construction in the governance inventory
test to use the registered mn_outpoint from GovernanceInvSetup while retaining
an invalid signature. Ensure the resulting orphan vote reaches invalid-signature
validation for the known masternode before caching, rather than the
unknown-masternode rejection path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d28af221-d493-4ccd-96b3-6de90538a389
📒 Files selected for processing (3)
src/governance/governance.cppsrc/governance/governance.hsrc/test/governance_inv_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
- src/governance/governance.h
- src/governance/governance.cpp
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f3a826a50
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The production reordering records failed AddTrigger attempts and correctly avoids scheduling rejected triggers for deferred relay. However, the flood protection remains bypassable on testnet, devnet, and regtest because failed attempts are measured using signer-controlled creation timestamps; the test suite also introduces avoidable repeated chain setup and a wall-clock-sensitive relay regression. Source: reviewers gpt-5.6-sol (general and dash-core-commit-history); final verifier gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking | 🟡 3 suggestion(s)
1 additional finding(s) omitted (not in diff).
1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
🤖 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 `<commit:6e468b85fba>`:
- [SUGGESTION] <commit:6e468b85fba>:1: Squash the review-driven test follow-ups into the fix
Commits 6e468b85fba and 7f3a826a504 are fixups to material introduced by c2d8a9cf154 in this same unshipped stack. The first deletes the standalone test file and Makefile entry introduced by c2d8a9cf154, relocates the regressions, and renames the new relay helper; the second immediately rewrites that relocation to replace the separate fixture and suite with the final unified setup. CONTRIBUTING.md identifies commits that repeatedly change the same code as fixup commits that may need squashing. Fold both follow-ups into c2d8a9cf154 so the final helper name, test location, and fixture arrangement land atomically without permanent add/delete and fixture-rewrite churn.
In `src/test/governance_inv_tests.cpp`:
- [SUGGESTION] src/test/governance_inv_tests.cpp:56-57: Keep lightweight INV tests off the chain fixture
BOOST_FIXTURE_TEST_SUITE constructs GovernanceInvSetup separately for every test case. Changing it to TestChainSetup means each of the six pre-existing lightweight INV/vote cases now mines 107 blocks, mines two additional blocks, registers a ProTx, and repeats chain/database initialization even though only the two new failed-trigger regressions use mn_outpoint or operator_key. That adds 654 unnecessary block constructions to the pre-existing cases. Keep their lightweight fixture and put the two signed-trigger cases under a separate chain fixture in this file.
- [SUGGESTION] src/test/governance_inv_tests.cpp:644-645: Freeze time in the deferred-relay regression
SetMockTime(0s) disables mock time because NodeClock::now() falls back to the system clock when the stored value is zero. The trigger is only ten seconds inside the deferred-relay arming interval: creation_time is captured_now + 3550, while scheduling requires it to remain greater than current_time + 3540. If signing and processing take ten seconds or more on an instrumented runner, even the buggy scheduling behavior does not queue the trigger and the regression passes falsely. Capture the current wall time and immediately freeze it at that positive value before constructing the trigger.
In `src/governance/governance.cpp`:
- [BLOCKING] src/governance/governance.cpp:714: Throttle failed triggers using non-attacker-controlled time
(existing thread: https://github.com/dashpay/dash/pull/7521#discussion_r3715379072)
The failed-trigger limiter records the operator-supplied creation timestamp, so the PR's flood protection remains bypassable on short-cycle networks. CRateCheckBuffer::GetRate() returns 5 / (max_timestamp - min_timestamp), while MasternodeRateCheck() accepts timestamps from now - 2 * cycle through now + 3600 and accepts rates below 2.2 / cycle. Testnet and default devnet use a 3600-second cycle, giving an accepted span of three cycles and an endpoint-spaced rate of 5 / (3 * cycle), below the limit. Regtest uses a 3000-second cycle and has a 3.2-cycle accepted span, which also passes. A valid operator can alternate unique, signature-valid malformed triggers near both endpoints so every rolling five-entry buffer retains the full span; each submission continues through BLS verification, AddTrigger, and mapObjects insertion. Mainnet's roughly 2.001-cycle window is too narrow for this technique, but the implementation and stated protection are network-generic. Account failed attempts using receipt time or another non-signer-controlled value, and add a regression using endpoint-spaced creation timestamps.
AddGovernanceObjectInternal emplaced a trigger into mapObjects, attempted AddTrigger, and on failure returned early before MasternodeRateUpdate. MasternodeRateCheck treats a missing rate-buffer entry as allow, so a masternode that never lands a successful trigger was never rate-limited at all. Each rejected trigger still cost a BLS verification and left a mapErasedGovernanceObjects entry retained for roughly 60 days on mainnet. Advance the rate buffer before the AddTrigger check so failed attempts count too. The deferred-relay scheduling that MasternodeRateUpdate also did is split out into ScheduleTriggerRelay and stays after AddTrigger succeeds; otherwise a trigger that was just undone and marked for deletion would be re-announced, and served on GETDATA, once it aged past RELIABLE_PROPAGATION_TIME. Both behaviours are covered in governance_vote_processing_tests, whose chain-backed fixture already registers a masternode with a known operator key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7f3a826 to
a551177
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
The complete diff at a551177 correctly counts locally validated failed triggers while keeping deferred relay scheduling after successful trigger registration. No in-scope defects were confirmed; the prior creation-time limiter concern is pre-existing and explicitly deferred. Verification was static only, and the supplied exact-head CI snapshot reports successful source builds, tests, and lint with no pending checks.
🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)
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); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 5: 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:
lowbygpt-6.1-sol(effort low) — The diff is a small, contained control-flow fix that counts rejected triggers in the existing rate buffer while preserving successful-trigger-only deferred relay scheduling, with focused regression tests. - Phase 1 reviewers:
glm-5.3-flash— general (completed, effort high); agentphase1-reviewer - Phase 1 model:
glm-5.3-flash— zai quota: 5h 91% left, weekly 94% left; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left) - Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— dash-core-commit-history (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— general (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— dash-core-commit-history (completed, effort medium); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.
No unresolved findings remain from the prior review on this head.
…rigger path a551177 fix: update masternode rate limit on failed governance trigger path (pasta) Pull request description: ## Issue being fixed or feature implemented In `AddGovernanceObjectInternal()`, a trigger object is emplaced into `mapObjects`, then `AddTrigger()` is attempted. On failure the function calls `PrepareDeletion()` and returns early, before `MasternodeRateUpdate()`. `MasternodeRateCheck()` allows any masternode that has no entry in `mapLastMasternodeObject`, and only `MasternodeRateUpdate()` creates that entry. A masternode that never lands a *successful* trigger therefore never gets a rate-buffer entry, and every malformed but signed trigger it sends passes the rate check. That defeats the limiter meant to stop exactly this. Each rejected trigger still costs a BLS verification, a `mapObjects` entry held for the deletion delay, and, after erasure, a `mapErasedGovernanceObjects` entry retained for about two superblock cycles (roughly 60 days on mainnet). Sending one needs a valid operator key for a masternode in the tip list, and the victim must have requested the hash after an INV. Rejected triggers are not relayed, so there is no fan-out; the attacker has to connect to each victim directly. ## What was done? - Call `MasternodeRateUpdate()` before the `AddTrigger` check, so failed attempts advance the masternode's rate buffer too. - `MasternodeRateUpdate()` also queued near-future triggers for the deferred re-announcement in `CheckPostponedObjects`. That part is split out into `ScheduleTriggerRelay()` and still runs only after `AddTrigger` succeeds. Otherwise moving the rate update would make the node re-announce, and serve on GETDATA, a trigger it had just undone and marked for deletion. ### Scope note: creation-time rate limiting on short-cycle networks The rate buffer stores the object's signer-chosen creation time, and `MasternodeRateCheck` accepts creation times from `now - 2 * cycle` to `now + 1h`, with a maximum rate of `2.2 / cycle` over five entries. On mainnet (cycle = 16616 blocks, about 28.8 days) the widest possible span is about `2 * cycle`, so a full buffer is always rejected (`5 / (2 * cycle)` = `2.5 / cycle` > `2.2 / cycle`). On testnet, devnet (1 h cycle) and regtest the accepted window is wider than `5 / 2.2` cycles, so an operator who alternates creation times between the window ends can stay under the rate. That weakness is in the existing limiter and applies equally to well-formed triggers today. This PR only brings failed triggers under the same limiter. Moving the limiter to receipt time would change how valid triggers are throttled and is left for a separate change. ## How Has This Been Tested? Two new cases in `src/test/governance_vote_processing_tests.cpp`, using its existing chain-backed fixture, which already registers a masternode with a known operator key. No new file or fixture, and `governance_inv_tests` is unchanged. - `failed_triggers_advance_the_masternode_rate_limit`: submits 12 signed triggers that fail `AddTrigger` and checks that exactly `RATE_BUFFER_SIZE - 1` (4) are stored. It fails with `12 != 4` on develop's `governance.cpp`/`governance.h`. - `failed_trigger_is_not_scheduled_for_deferred_relay`: a failed trigger created inside the deferred-relay window is never announced, even after it ages past `RELIABLE_PROPAGATION_TIME`. It fails when `ScheduleTriggerRelay()` is called before `AddTrigger`, which is the regression the split prevents. Run locally on macOS arm64 with `--enable-debug --enable-werror`: - `src/test/test_dash --run_test=governance_vote_processing_tests,governance_inv_tests,governance_superblock_tests,governance_validators_tests` passes. - `test/functional/test_runner.py feature_governance.py feature_governance_objects.py feature_governance_cl.py` passes. - `test/lint/lint-whitespace.py` passes. ## Breaking Changes None. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone Top commit has no ACKs. Tree-SHA512: 2892edb480e2c3c63c1be7d231d745ecea2d2dcf6161f48ec0cd3f3e6484a120af77de540db06e3e7892840dc6a1fb234df98025686fb27a121731a7996338ac (cherry picked from commit 8db4b76)
6c02b2b ci: let DEPENDS_ARCH override the machine arch for multiprocess and tsan (pasta) ce5dbf3 Merge #7888: fix(consensus): canonical ExtNetInfo encoding and strict netInfo decoding (pasta) 54cba5c Merge #7537: fix: data races and check-then-act races in the CoinJoin server (pasta) 08d8bca Merge #7909: feat(mining): build the masternode list incrementally during block assembly (pasta) 5633761 Merge #7895: test: cover retroactive provider tx refusal, fix wording (pasta) cbb9500 Merge #7846: fix: treat pending owner keys and Platform node IDs as one conflict namespace (pasta) 8efdf5e Merge #7857: fix: mine a pending ProUpServTx before a key change of its masternode (pasta) e56e6ae Merge #7842: fix(wallet): reject mnemonic passphrases that seed derivation truncates (pasta) 79480ff test: follow-up for #7821: check if wallet is locked after failed upgrade instead creating brand-new scenario (Konstantin Akimov) a56ed47 fix(net): announce sporks to new peers instead of requesting them during mnsync (pasta) cdaea5f Merge #7894: fix: due to logical conflicts feature_pruning.py had been broken while (pasta) 98d92f6 Merge #7817: backport: partial bitcoin#34156 restore cleanup ownership (pasta) 35e6d18 Merge #7858: fix: do not InstantSend-lock provider transactions (pasta) 904d73d Merge #7881: fix: refuse to sign shared registrar updates that move the funder's coins (pasta) 95982f5 Merge #7782: fix(wallet): persist pending-observation coin locks once the record becomes readable (pasta) cdf65d0 Merge #7836: backport: partial bitcoin#25812, bitcoin#29855 (pasta) cc5716f Merge #7824: fix: retain initialized DKG sessions during inventory reads (pasta) 588e458 Merge #7827: backport: partial bitcoin#35984 (pasta) 795c6f1 Merge #7839: fix: release InstantSend tracking after mempool eviction (pasta) dc1a6b2 Merge #7875: docs(rpc): clarify getchainlockbyheight availability (pasta) e4798ff Merge #7876: fix(consensus): capture operator key scheme changes with unchanged bytes in state diffs (pasta) fe68ecb Merge #7871: backport!: Merge bitcoin#27217: wallet: Replace use of purpose strings with an enum (pasta) 8348d22 Merge #7867: fix: refuse masternode list RPCs for blocks outside the active chain (pasta) 861a579 Merge #7866: fix: stop `masternode payments` at the genesis block (pasta) efcef71 Merge #7521: fix: update masternode rate limit on failed governance trigger path (pasta) 0623182 Merge #7831: fix: bound governance object signatures and reset the governance cache (pasta) b230657 Merge #7841: fix: authenticate cached InstantSend proof bytes (pasta) a1f37ea Merge #7820: fix: hold chainstate lock during CoinJoin input validation (pasta) 50a5617 Merge #7829: fix: preserve wallet permissions when changing passphrases (pasta) 2ff0103 Merge #7818: fix: return REST errors for unavailable deployment data (pasta) cb39369 Merge #7834: fix: validate Electrum import input boundaries (pasta) e7e3daa Merge #7821: fix: relock encrypted wallets after HD upgrade failures (pasta) 2fea45f Merge #7792: fix: treat missing snapshots during evodb repair as repair errors (pasta) 3c5c039 Merge #7838: fix(wallet): authenticate unlock deadline extensions (pasta) 0bf80f8 Merge #7810: fix(rpc): handle unavailable masternode payment inputs (pasta) bcefee4 Merge #7822: fix: reject malformed detached quorum share batches (pasta) 86a7737 Merge #7828: fix: omit recovery words from invalid mnemonic errors (pasta) 886d5c4 Merge #7816: fix: check credit pool amount accumulation (pasta) 00fc3b9 Merge #7851: fix(rpc): correct masternode status help for top-level service field (pasta) 0e75c22 Merge #7814: fix(qt): restore HTML escaping of send confirmation recipients (pasta) Pull request description: ## Issue being fixed or feature implemented Backports for v24.0.0-rc.4, on top of `v24.0.x` at the v24.0.0-rc.3 tag (0364ed6, #7808). ## What was done? Cherry-picked (`git cherry-pick -m1 -x`) every develop merge since rc.3 that carries `backport-candidate-24.0.x`, plus eight unlabeled fixes picked after an audit of every other develop merge since rc.3 (marked † below). They are in develop merge order: - #7814 fix(qt): restore HTML escaping of send confirmation recipients † - #7851 fix(rpc): correct masternode status help for top-level service field - #7816 fix: check credit pool amount accumulation - #7828 fix: omit recovery words from invalid mnemonic errors † - #7822 fix: reject malformed detached quorum share batches † - #7810 fix(rpc): handle unavailable masternode payment inputs † - #7838 fix(wallet): authenticate unlock deadline extensions - #7792 fix: treat missing snapshots during evodb repair as repair errors - #7821 fix: relock encrypted wallets after HD upgrade failures - #7834 fix: validate Electrum import input boundaries - #7818 fix: return REST errors for unavailable deployment data - #7829 fix: preserve wallet permissions when changing passphrases - #7820 fix: hold chainstate lock during CoinJoin input validation - #7841 fix: authenticate cached InstantSend proof bytes - #7831 fix: bound governance object signatures and reset the governance cache - #7521 fix: update masternode rate limit on failed governance trigger path - #7866 fix: stop `masternode payments` at the genesis block - #7867 fix: refuse masternode list RPCs for blocks outside the active chain † - #7871 backport!: bitcoin#27217: wallet: Replace use of purpose strings with an enum - #7876 fix(consensus): capture operator key scheme changes with unchanged bytes in state diffs - #7875 docs(rpc): clarify getchainlockbyheight availability † - #7839 fix: release InstantSend tracking after mempool eviction - #7827 backport: partial bitcoin#35984 - #7824 fix: retain initialized DKG sessions during inventory reads - #7836 backport: partial bitcoin#25812, bitcoin#29855 - #7782 fix(wallet): persist pending-observation coin locks once the record becomes readable - #7881 fix: refuse to sign shared registrar updates that move the funder's coins † - #7858 fix: do not InstantSend-lock provider transactions - #7817 backport: partial bitcoin#34156 restore cleanup ownership - #7894 test: fix feature_pruning.py restart arguments † (needed because #7792 makes the evodb repair fatal, which the old restart arguments trip) - #7893 fix(net): announce sporks to new peers instead of requesting them during mnsync - 4a19742 (from #7859) test: follow-up for #7821: check if wallet is locked after failed upgrade instead creating brand-new scenario. Test-only, picked as its commit. Without it, #7821's `test_failed_upgrade()` expects RPC error -1 for a 257-byte mnemonic passphrase, which #7842's new precheck rejects with -8 first, so `wallet_upgradetohd.py --legacy-wallet` fails. On develop, #7859 replaced that scenario before #7842 merged. The rest of #7859 is test cleanup and docs and is left out. - #7842 fix(wallet): reject mnemonic passphrases that seed derivation truncates - #7857 fix: mine a pending ProUpServTx before a key change of its masternode - #7846 fix: treat pending owner keys and Platform node IDs as one conflict namespace - #7895 test: cover retroactive provider tx refusal, fix wording - #7909 feat(mining): build the masternode list incrementally during block assembly - #7537 fix: data races and check-then-act races in the CoinJoin server - #7888 fix(consensus): canonical ExtNetInfo encoding and strict netInfo decoding 34 of the 39 picks change exactly the same lines as their develop merges. #7888 and #7782 needed test-only changes (see testing), and three others needed adaptation, described in their commit messages: - **#7893** was merged to develop twice: f99212b with an earlier revision of its commit, then 72a5997 with the final one, so develop now announces sporks twice in the VERACK handler (a develop fix is open separately). Here the PR's final commit 45acdf3 is picked once instead of either merge. In `src/node/sync_manager.cpp`, this branch still sent `GETSPORKS` from the mnsync tick on every network, because #7847, which limited it to test chains, is not included. The request is removed entirely, as on develop. Mainnet spork values are hardcoded in `CSporkManager::GetSporkValue`, so the mainnet request had no effect. - **#7857** and **#7909**: develop folded `CChainstateHelper` into `Chainstate` (d5c1bd7), which is not on this branch, so the new code goes through `ChainHelper().special_tx` / `m_chain_helper.special_tx` instead of `m_special_tx`. The neighbouring develop-only `CSpecialTxProcessor::GetDeterministicMNListHash` is left out. #7888 tightens consensus rules for ExtAddr provider transactions behind V24, so I checked it against testnet. V24 is not active there yet (the tip's CbTx is still version 3), so the chain has no ExtAddr ProTxs the new rules could reject. rc.3's own serializer always writes canonical, sorted purposes, so every ProTx it builds passes the new rules. Mixed rc.3/rc.4 networks could only split on a hand-crafted non-canonical ExtAddr ProTx mined after V24 activates. So testnet miners and masternodes should move to rc.4 before V24 is signalled. Unlabeled develop fixes since rc.3 that were audited and left out: - Worth considering before the final release: #7869 (startup block replay after an unclean shutdown asserts on a quorum commitment, so recovery needs `-reindex-chainstate`; not a v24 regression), #7833 (gate signing intake before quorum retrieval, DoS hardening), #7682 (intermittent `relic_conf.h` parallel-build failure). - Left for 24.1: #7812, #7826, #7844, #7847, #7850, #7868, plus the routine upstream backports (#7780, #7806, #7830, #7855, #7856, #7862, #7863, #7865, #7883, #7884, #7898, #7906), refactors (#7805, #7878, #7880), and CI, docs and test-only changes. One CI-only commit comes last. It takes only the `ci/test` hunks of 6f71fa5 (#7901), which make `ci/test/00_setup_env_native_{multiprocess,tsan}.sh` honor `DEPENDS_ARCH`. Pull requests into `v24.0.x` run develop's workflow files (`pull_request_target`), and since #7901 those check the depends cache on x86_64 runners and pass the aarch64 build arch in `DEPENDS_ARCH`. Without the hunks, `linux64_multiprocess / Build depends` fails on any depends cache miss (`Build runner computes HOST=aarch64-linux-gnu but check-cache used x86_64-pc-linux-gnu`). Without `DEPENDS_ARCH` the scripts still use `uname -m`, so this branch's own push CI is unchanged. 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.4` tag. ## How Has This Been Tested? Built and tested at 6c02b2b on macOS arm64 against depends, with `--enable-debug --enable-werror --disable-hardening`. - `make check`: all unit test suites pass, including `test_dash-qt`. - Functional tests, all passing: `feature_evo_shares_activation.py`, `feature_governance_objects.py`, `feature_llmq_signing.py` (plain and `--spork21`), `feature_masternode_shares.py`, `feature_sporks.py`, `interface_rest.py`, `p2p_instantsend.py`, `p2p_quorum_data.py`, `rpc_masternode.py`, `rpc_psbt.py`, `rpc_quorum.py`, `wallet_backup.py`, `wallet_dump.py`, `wallet_encryption.py`, `wallet_labels.py` and `wallet_upgradetohd.py`, each with `--descriptors` and `--legacy-wallet` where it supports them. - Each pick's changed lines were compared with its develop merge (or, for #7893, its PR commit). They are identical except for the #7893, #7857 and #7909 adaptations above, and the #7888 test adaptation below. - A local macOS arm64 debug build (`--enable-debug --enable-werror`) compiled most of the tree and stopped on a single error in `src/test/evo_netinfo_tests.cpp`. #7888's new test used `CNetAddr::V2()`, the serialization-parameter API from bitcoin#25284, which this branch lacks. The pick now writes that entry through an `OverrideStream` with `ADDRV2_FORMAT`, the way `NetInfoEntry::Serialize` does on this branch (see its commit message). - #7782's new unit test reloads the wallet with develop's three-argument `CWallet` constructor. On this branch the constructor still takes the CoinJoin loader and an `ArgsManager`, so the test passes them the way its fixture already does (see the commit message). - With both test fixes, a full local `make` (`dashd`, `dash-qt`, `test_dash`, `test_dash-qt`) completes with no errors. - `feature_pruning.py` is an extended test that CI does not run. #7894 is included because #7792 makes its restart path fail. ## Breaking Changes Carried over from the picked PRs: - #7876: rc.1 to rc.3 nodes that connected an affected block stored a wrong masternode list diff for it. Those nodes need `-reindex`; `-reindex-chainstate` is not enough for nodes that already marked the following block invalid. - #7888: stricter ExtAddr `netInfo` decoding and validation behind V24 (see above). The RFC1918 check now applies even with `-allowprivatenet`. - #7858: provider transactions no longer get InstantSend locks. Software that waits for an islock on a ProTx should wait for a ChainLock or confirmations instead. - #7857: a `ProUpServTx` spending an output of a pending operator key change, asset lock, asset unlock or MNHF signal is rejected from the mempool until that transaction confirms. - #7831: governance object signatures over 96 bytes are rejected, and the governance cache is discarded and re-synced on first start (`doc/release-notes-7831.md`). - #7871: address purposes are limited to `send`, `receive` and `refund`. Wallets with other purpose strings load with warnings, and `listlabels` errors on an unknown purpose (`doc/release-notes-27217.md`). - #7842: creating an HD wallet with a mnemonic passphrase over 248 bytes fails unless `-allow-deprecated-long-mnemonics` is set, and over 256 bytes always fails (`doc/release-notes-7842.md`). - #7792: a node whose evodb is missing a masternode list snapshot now stops at startup and asks for `-reindex`, and `evodb repair` reports it in `repairErrors`. - #7867: `protx info <proTxHash> <blockHash>`, `protx listdiff` and `masternode payments <blockHash>` return an error for blocks outside the active chain. - #7881: `protx shared_combine` on a `ProUpSharedRegTx` is refused unless its inputs are unlocked coins of a single address of the calling wallet, change returns to that address, and the fee is within `-maxtxfee`. - #7836: PSBTs referencing nonexistent previous outputs fail to deserialize (RPC error -22). - #7834: negative Electrum-import rescan indices are rejected. - #7893: on this branch, mainnet nodes no longer send `GETSPORKS` during mnsync (no effect, mainnet spork values are hardcoded). ## 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: 7de9535ca51620f4ddf5a46df2c3341a5e5fdd2263606f83f7db6b576c31ff7b82e1b285bf66988ac4b2729a9060513c45710135f61d7fd5c147b670d5f405dc
Issue being fixed or feature implemented
In
AddGovernanceObjectInternal(), a trigger object is emplaced intomapObjects, thenAddTrigger()is attempted. On failure the function callsPrepareDeletion()and returns early, beforeMasternodeRateUpdate().MasternodeRateCheck()allows any masternode that has no entry inmapLastMasternodeObject, and onlyMasternodeRateUpdate()creates that entry. A masternode that never lands a successful trigger therefore never gets a rate-buffer entry, and every malformed but signed trigger it sends passes the rate check. That defeats the limiter meant to stop exactly this.Each rejected trigger still costs a BLS verification, a
mapObjectsentry held for the deletion delay, and, after erasure, amapErasedGovernanceObjectsentry retained for about two superblock cycles (roughly 60 days on mainnet).Sending one needs a valid operator key for a masternode in the tip list, and the victim must have requested the hash after an INV. Rejected triggers are not relayed, so there is no fan-out; the attacker has to connect to each victim directly.
What was done?
MasternodeRateUpdate()before theAddTriggercheck, so failed attempts advance the masternode's rate buffer too.MasternodeRateUpdate()also queued near-future triggers for the deferred re-announcement inCheckPostponedObjects. That part is split out intoScheduleTriggerRelay()and still runs only afterAddTriggersucceeds. Otherwise moving the rate update would make the node re-announce, and serve on GETDATA, a trigger it had just undone and marked for deletion.Scope note: creation-time rate limiting on short-cycle networks
The rate buffer stores the object's signer-chosen creation time, and
MasternodeRateCheckaccepts creation times fromnow - 2 * cycletonow + 1h, with a maximum rate of2.2 / cycleover five entries. On mainnet (cycle = 16616 blocks, about 28.8 days) the widest possible span is about2 * cycle, so a full buffer is always rejected (5 / (2 * cycle)=2.5 / cycle>2.2 / cycle). On testnet, devnet (1 h cycle) and regtest the accepted window is wider than5 / 2.2cycles, so an operator who alternates creation times between the window ends can stay under the rate. That weakness is in the existing limiter and applies equally to well-formed triggers today. This PR only brings failed triggers under the same limiter. Moving the limiter to receipt time would change how valid triggers are throttled and is left for a separate change.How Has This Been Tested?
Two new cases in
src/test/governance_vote_processing_tests.cpp, using its existing chain-backed fixture, which already registers a masternode with a known operator key. No new file or fixture, andgovernance_inv_testsis unchanged.failed_triggers_advance_the_masternode_rate_limit: submits 12 signed triggers that failAddTriggerand checks that exactlyRATE_BUFFER_SIZE - 1(4) are stored. It fails with12 != 4on develop'sgovernance.cpp/governance.h.failed_trigger_is_not_scheduled_for_deferred_relay: a failed trigger created inside the deferred-relay window is never announced, even after it ages pastRELIABLE_PROPAGATION_TIME. It fails whenScheduleTriggerRelay()is called beforeAddTrigger, which is the regression the split prevents.Run locally on macOS arm64 with
--enable-debug --enable-werror:src/test/test_dash --run_test=governance_vote_processing_tests,governance_inv_tests,governance_superblock_tests,governance_validators_testspasses.test/functional/test_runner.py feature_governance.py feature_governance_objects.py feature_governance_cl.pypasses.test/lint/lint-whitespace.pypasses.Breaking Changes
None.
Checklist: