Skip to content

fix: index rotated quorums by quorum_index and rebuild per cycle#637

Open
xdustinface wants to merge 2 commits intov0.42-devfrom
fix/rotated-quorums-index
Open

fix: index rotated quorums by quorum_index and rebuild per cycle#637
xdustinface wants to merge 2 commits intov0.42-devfrom
fix/rotated-quorums-index

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented Apr 11, 2026

The Vec<QualifiedQuorumEntry> had no size bound and no uniqueness check on quorum_index. Combined with QRInfo still being requested on every new block (will change soon), repeated feed_qr_info calls within a single rotation cycle accumulated duplicate entries that the lookup then returned as stale matches.

  • Switch the inner container to BTreeMap<u16, QualifiedQuorumEntry> keyed by quorum_index so insertion replaces by key, lookup is direct, and re-feeds of the same cycle are not causing wrong behaviour anymore.
  • Introduce insert_cycle_quorum_by_index as helper to centralize inserts.
  • Both branches of feed_qr_info now .clear() the per-cycle map before refilling it and clearing is enforced via a debug assert.
  • Migrates tests/data/test_DML_diffs/masternode_list_engine.hex to the new format.

Summary by CodeRabbit

  • Performance Improvements

    • Faster and more efficient quorum lookup and selection for masternode operations, improving responsiveness.
  • Reliability & Stability

    • Stronger validation and stricter checks for rotating quorums, including protection against invalid or duplicate quorum indices and size mismatches.
    • Improved error reporting when a quorum index is invalid or missing.
  • Tests

    • Updated and expanded tests covering quorum indexing edge cases and validation scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

Warning

Rate limit exceeded

@xdustinface has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 15 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 33 minutes and 15 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0c0961bd-3280-46b2-b0db-4b04dc87b711

📥 Commits

Reviewing files that changed from the base of the PR and between 9c9835f and aa3ec78.

📒 Files selected for processing (1)
  • dash/src/sml/masternode_list_engine/mod.rs
📝 Walkthrough

Walkthrough

Rotated quorum storage changed from per-cycle Vec to per-cycle BTreeMap keyed by quorum_index; lookup switched from linear search to direct map access. New validation builds per-cycle maps (rejecting missing/invalid/duplicate indices and wrong counts). QuorumValidationError gained an InvalidQuorumIndex variant.

Changes

Cohort / File(s) Summary
Quorum Lookup & Tests
dash/src/sml/masternode_list_engine/message_request_verification.rs
is_lock_potential_quorums now returns &BTreeMap<u16, QualifiedQuorumEntry>; is_lock_quorum uses quorums.get(&quorum_index) instead of iterating. Cycle-hash debug logging iterates map entries. Tests updated to remove vector-order assumptions and to remove quorum entries by key.
Engine Storage, Population & Validation
dash/src/sml/masternode_list_engine/mod.rs
rotated_quorums_per_cycle type changed from BTreeMap<BlockHash, Vec<QualifiedQuorumEntry>> to BTreeMap<BlockHash, BTreeMap<u16, QualifiedQuorumEntry>>. Added feature-gated build_cycle_quorum_map(...) which rejects missing/invalid/duplicate indices and enforces expected quorum count. Feed/population logic updated to construct per-cycle map and store it; iteration sites changed to use cycle_quorums.values(). New unit tests added for edge cases (missing index, invalid/negative index, duplicate index, wrong count).
Validation Error Type
dash/src/sml/quorum_validation_error.rs
Added QuorumValidationError::InvalidQuorumIndex { quorum_hash: QuorumHash, index: i16 } to report invalid quorum index values encountered during map building/validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through lists of quorums once long,

Now keyed by index, my hopping's more strong.
No more linear searches to make me sigh,
I map every quorum beneath the sky.
Validated, indexed — I cheer and I prance!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main structural change: refactoring rotated quorums to be indexed by quorum_index and rebuilt per cycle, which directly addresses the core problem of duplicate entries from repeated feeds.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rotated-quorums-index

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 and usage tips.

@xdustinface xdustinface marked this pull request as draft April 11, 2026 17:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
dash/src/sml/masternode_list_engine/mod.rs (1)

747-751: Consider replacing .unwrap() with safer alternative.

While the unwrap() at line 748 is safe because the loop only executes when qualified_last_commitment_per_index is non-empty (which guarantees the Option is Some from the .first().map() at lines 724-729), the coding guidelines discourage unwrap() in library code.

♻️ Suggested safer alternative
-                insert_cycle_quorum_by_index(
-                    qualified_rotated_quorums_per_cycle.as_mut().unwrap(),
-                    rotated_quorum.clone(),
-                    rotation_quorum_type,
-                )?;
+                if let Some(map) = qualified_rotated_quorums_per_cycle.as_mut() {
+                    insert_cycle_quorum_by_index(
+                        map,
+                        rotated_quorum.clone(),
+                        rotation_quorum_type,
+                    )?;
+                }

As per coding guidelines: "/src//*.rs: Avoid unwrap() and expect() in library code; use proper error types".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dash/src/sml/masternode_list_engine/mod.rs` around lines 747 - 751, Replace
the direct .unwrap() on qualified_rotated_quorums_per_cycle with a safe pattern:
check the Option (e.g., if let Some(ref mut qualified_rotated_quorums) =
qualified_rotated_quorums_per_cycle) and call
insert_cycle_quorum_by_index(qualified_rotated_quorums, rotated_quorum.clone(),
rotation_quorum_type) inside that branch, otherwise return an Err with an
appropriate error variant; reference the surrounding logic that sets
qualified_rotated_quorums_per_cycle from qualified_last_commitment_per_index
(the .first().map() earlier) to craft a meaningful error message if the Option
is None.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@dash/src/sml/masternode_list_engine/mod.rs`:
- Around line 747-751: Replace the direct .unwrap() on
qualified_rotated_quorums_per_cycle with a safe pattern: check the Option (e.g.,
if let Some(ref mut qualified_rotated_quorums) =
qualified_rotated_quorums_per_cycle) and call
insert_cycle_quorum_by_index(qualified_rotated_quorums, rotated_quorum.clone(),
rotation_quorum_type) inside that branch, otherwise return an Err with an
appropriate error variant; reference the surrounding logic that sets
qualified_rotated_quorums_per_cycle from qualified_last_commitment_per_index
(the .first().map() earlier) to craft a meaningful error message if the Option
is None.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a849f286-b84f-4ddd-b04e-9fc98bf65d33

📥 Commits

Reviewing files that changed from the base of the PR and between a7808ca and 31f7258.

📒 Files selected for processing (4)
  • dash/src/sml/masternode_list_engine/message_request_verification.rs
  • dash/src/sml/masternode_list_engine/mod.rs
  • dash/src/sml/quorum_validation_error.rs
  • dash/tests/data/test_DML_diffs/masternode_list_engine.hex

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 11, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 85.21739% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.90%. Comparing base (383b306) to head (aa3ec78).

Files with missing lines Patch % Lines
dash/src/sml/masternode_list_engine/mod.rs 84.76% 16 Missing ⚠️
...ernode_list_engine/message_request_verification.rs 90.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #637      +/-   ##
=============================================
+ Coverage      67.48%   67.90%   +0.42%     
=============================================
  Files            246      318      +72     
  Lines          49280    67832   +18552     
=============================================
+ Hits           33256    46063   +12807     
- Misses         16024    21769    +5745     
Flag Coverage Δ
core 75.27% <85.21%> (+0.05%) ⬆️
ffi 38.62% <ø> (+2.24%) ⬆️
rpc 20.00% <ø> (ø)
spv 85.54% <ø> (+0.15%) ⬆️
wallet 67.56% <ø> (?)
Files with missing lines Coverage Δ
dash/src/sml/quorum_validation_error.rs 37.50% <ø> (ø)
...ernode_list_engine/message_request_verification.rs 82.03% <90.00%> (-1.08%) ⬇️
dash/src/sml/masternode_list_engine/mod.rs 74.89% <84.76%> (+1.73%) ⬆️

... and 91 files with indirect coverage changes

@xdustinface xdustinface force-pushed the fix/rotated-quorums-index branch from a82c35c to 7d9305a Compare April 13, 2026 11:04
The `Vec<QualifiedQuorumEntry>` had no size bound and no uniqueness check on `quorum_index`. Combined with QRInfo still being requested on every new block (will change soon), repeated `feed_qr_info` calls within a single rotation cycle accumulated duplicate entries that the lookup then returned as stale matches.

- Switch the inner container to `BTreeMap<u16, QualifiedQuorumEntry>` keyed by `quorum_index` so insertion replaces by key, lookup is direct, and re-feeds of the same cycle are not causing wrong behaviour anymore.
- Introduce `build_cycle_quorum_map` to centralize map construction with index validation and count checks. Both branches of `feed_qr_info` use it to rebuild the per-cycle map from scratch.
- Migrates `tests/data/test_DML_diffs/masternode_list_engine.hex` to the new format.
@xdustinface xdustinface force-pushed the fix/rotated-quorums-index branch from 7d9305a to 9c9835f Compare April 13, 2026 14:07
@xdustinface xdustinface marked this pull request as ready for review April 13, 2026 14:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dash/src/sml/masternode_list_engine/mod.rs`:
- Around line 722-724: The code currently sets cycle_key by taking the first
element of qualified_last_commitment_per_index (cycle_key =
qualified_last_commitment_per_index.first().map(|q|
q.quorum_entry.quorum_hash)), which is wrong if the vector is out of order;
change the lookup to find the entry whose quorum_entry.quorum_index == 0 and use
its quorum_hash (i.e., use .iter().find(|q| q.quorum_entry.quorum_index ==
0).map(|q| q.quorum_entry.quorum_hash)) so the cycle is always keyed by index 0;
make this change in both the verifying branch (where cycle_key is currently set)
and the non‑verifying branch (the analogous assignment mentioned for Lines
856–861 and 759–765) so both branches derive cycle_key from quorum_index == 0.
- Around line 225-232: The current validation only checks map.len() against
expected (rotation_quorum_type.active_quorum_count()), which allows missing or
non-zero-based quorum_index keys; update validation to ensure the full
quorum_index range 0..expected-1 exists by iterating each expected index and
verifying map.get(index) is Some (or map.contains_key(index)) and return
QuorumValidationError::CorruptedCodeExecution with a clear message if any index
is missing; reference the existing symbols rotation_quorum_type, expected, map
and quorum_index when adding this explicit per-index check so downstream code
that does direct get(index) lookups can safely assume index 0..expected-1 are
present.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a114604-c50c-4d7f-be19-ba011eb95168

📥 Commits

Reviewing files that changed from the base of the PR and between 31f7258 and 9c9835f.

📒 Files selected for processing (4)
  • dash/src/sml/masternode_list_engine/message_request_verification.rs
  • dash/src/sml/masternode_list_engine/mod.rs
  • dash/src/sml/quorum_validation_error.rs
  • dash/tests/data/test_DML_diffs/masternode_list_engine.hex
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash/src/sml/quorum_validation_error.rs

The existing count check allowed a set like `{1, 2, ..., expected}` to pass - distinct indices with the right total, but missing index 0 and containing one out-of-range index. Downstream code assumes contiguous `[0, expected)` coverage and breaks on direct `get(&i)` lookups. Add a per-entry upper-bound guard inside the loop so the offending index is reported precisely; combined with the existing no-duplicates and count checks, this guarantees `keys == {0, 1, ..., expected - 1}` exactly.

Addresses CodeRabbit review comment on PR #637
#637 (comment)
xdustinface added a commit to xdustinface/rust-dashcore that referenced this pull request Apr 13, 2026
The existing count check allowed a set like `{1, 2, ..., expected}` to pass - distinct indices with the right total, but missing index 0 and containing one out-of-range index. Downstream code assumes contiguous `[0, expected)` coverage and breaks on direct `get(&i)` lookups. Add a per-entry upper-bound guard inside the loop so the offending index is reported precisely; combined with the existing no-duplicates and count checks, this guarantees `keys == {0, 1, ..., expected - 1}` exactly.

Addresses CodeRabbit review comment on PR dashpay#637
dashpay#637 (comment)
xdustinface added a commit to xdustinface/rust-dashcore that referenced this pull request Apr 13, 2026
The existing count check allowed a set like `{1, 2, ..., expected}` to pass - distinct indices with the right total, but missing index 0 and containing one out-of-range index. Downstream code assumes contiguous `[0, expected)` coverage and breaks on direct `get(&i)` lookups. Add a per-entry upper-bound guard inside the loop so the offending index is reported precisely; combined with the existing no-duplicates and count checks, this guarantees `keys == {0, 1, ..., expected - 1}` exactly.

Addresses CodeRabbit review comment on PR dashpay#637
dashpay#637 (comment)
xdustinface added a commit to xdustinface/rust-dashcore that referenced this pull request Apr 13, 2026
The existing count check allowed a set like `{1, 2, ..., expected}` to pass - distinct indices with the right total, but missing index 0 and containing one out-of-range index. Downstream code assumes contiguous `[0, expected)` coverage and breaks on direct `get(&i)` lookups. Add a per-entry upper-bound guard inside the loop so the offending index is reported precisely; combined with the existing no-duplicates and count checks, this guarantees `keys == {0, 1, ..., expected - 1}` exactly.

Addresses CodeRabbit review comment on PR dashpay#637
dashpay#637 (comment)
xdustinface added a commit to xdustinface/rust-dashcore that referenced this pull request Apr 13, 2026
The existing count check allowed a set like `{1, 2, ..., expected}` to pass - distinct indices with the right total, but missing index 0 and containing one out-of-range index. Downstream code assumes contiguous `[0, expected)` coverage and breaks on direct `get(&i)` lookups. Add a per-entry upper-bound guard inside the loop so the offending index is reported precisely; combined with the existing no-duplicates and count checks, this guarantees `keys == {0, 1, ..., expected - 1}` exactly.

Addresses CodeRabbit review comment on PR dashpay#637
dashpay#637 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant