Skip to content

fix: resolve just-retired quorum by walking earlier masternode lists#801

Open
xdustinface wants to merge 1 commit into
devfrom
fix/quorum-lookup-retired-quorum
Open

fix: resolve just-retired quorum by walking earlier masternode lists#801
xdustinface wants to merge 1 commit into
devfrom
fix/quorum-lookup-retired-quorum

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented Jun 2, 2026

get_quorum_at_height and the FFI ffi_dash_spv_get_quorum_public_key resolved a quorum only through the single nearest masternode list at or below the lookup height. A Platform signing quorum is selected at a lagged height, so by the proof's core_chain_locked_height it can already have retired out of the active set. apply_diff drops a retired quorum from every later list, so the nearest list misses and the lookup fails with Quorum not found, surfacing downstream as InvalidQuorum. The failure is intermittent, firing only at the retirement edge.

The full entry is not actually gone: apply_diff clones the base list without mutating earlier ones, and old per-height lists are retained, so the quorum still lives in every list from its mint height up to retirement.

Add MasternodeListEngine::quorum_entry_for_hash_at_or_before_height, which walks masternode_lists backward from the lookup height and returns the first list still holding the quorum, skipping Invalid entries. Both consumer sites route through it, so the real full QualifiedQuorumEntry is returned with no synthesized data, no new storage, and no API change. The walk is floored at a few active windows below the height (derived from the type's signing_active_quorum_count and DKG interval): a legitimately referenced signing quorum cannot be older than that, so a miss scans a bounded span rather than every accumulated list.

Closes #800.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Optimized quorum lookup mechanism for improved reliability and performance when resolving quorums at specific block heights
    • Enhanced error messages when quorum resolution fails, now providing specific details about quorum type, requested height, and quorum hash

`get_quorum_at_height` and the FFI `ffi_dash_spv_get_quorum_public_key` resolved a quorum only through the single nearest masternode list at or below the lookup height. A Platform signing quorum is selected at a lagged height, so by the proof's `core_chain_locked_height` it can already have retired out of the active set. `apply_diff` drops a retired quorum from every later list, so the nearest list misses and the lookup fails with `Quorum not found`, surfacing downstream as `InvalidQuorum`. The failure is intermittent, firing only at the retirement edge.

The full entry is not actually gone: `apply_diff` clones the base list without mutating earlier ones, and old per-height lists are retained, so the quorum still lives in every list from its mint height up to retirement.

Add `MasternodeListEngine::quorum_entry_for_hash_at_or_before_height`, which walks `masternode_lists` backward from the lookup height and returns the first list still holding the quorum, skipping `Invalid` entries. Both consumer sites route through it, so the real full `QualifiedQuorumEntry` is returned with no synthesized data, no new storage, and no API change. The walk is floored at a few active windows below the height (derived from the type's `signing_active_quorum_count` and DKG interval): a legitimately referenced signing quorum cannot be older than that, so a miss scans a bounded span rather than every accumulated list.

Closes #800.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new quorum lookup API in MasternodeListEngine::quorum_entry_for_hash_at_or_before_height() that searches retained masternode lists within a computed active-window floor, and refactors both the dash-spv client library and FFI layers to use this direct hash-based resolution instead of manual list iteration.

Changes

Quorum hash-based lookup refactor

Layer / File(s) Summary
Quorum lookup engine and windowing
dash/src/sml/masternode_list_engine/helpers.rs
Adds QUORUM_WALK_BACK_ACTIVE_WINDOWS constant and quorum_entry_for_hash_at_or_before_height() function that computes an active-window floor from LLMQType parameters, iterates retained masternode lists in descending order, filters out Invalid entries, and returns the originating list height with the resolved QualifiedQuorumEntry. Includes tests for active/retired quorum resolution, absence cases, Invalid-entry skipping, and active-window boundary enforcement.
Dash-spv client queries refactor
dash-spv/src/client/queries.rs
get_quorum_at_height() now calls quorum_entry_for_hash_at_or_before_height() directly instead of manually constructing masternode lists and searching nested quorum maps. Returns the cloned quorum on match with debug logging; on miss, emits a single warning and returns SpvError::QuorumLookupError with a simplified error message.
FFI platform integration refactor
dash-spv-ffi/src/platform_integration.rs
ffi_dash_spv_get_quorum_public_key() now resolves the quorum entry via quorum_entry_for_hash_at_or_before_height(), copies the 48-byte public key to out_pubkey on success, and returns ValidationError with a focused message (quorum type, height, hash) on miss.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A quorum sought through time and hash,
Now walks the windows back with dash,
No list-by-list, just one clean call—
The hash unlocks the quorum's hall!
Retired or new, we find them all.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements a different solution than stated in issue #800. Issue #800 goals specify adding quorum_public_key_by_hash() to read quorum_statuses, but the PR instead adds quorum_entry_for_hash_at_or_before_height() to walk earlier masternode lists. Implement the two stated goals from #800: (1) add MasternodeListEngine::quorum_public_key_by_hash() to expose quorum_statuses, and (2) modify get_quorum_at_height to fall back to by-hash lookup on active-list miss.
✅ Passed checks (4 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 change—adding backward walk through masternode lists to resolve just-retired quorums—which directly addresses the core fix implemented in the PR.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing a solution for quorum lookup, but the approach diverges from the stated linked issue requirements and introduces new logic not mentioned in issue #800.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/quorum-lookup-retired-quorum

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 ready for review June 2, 2026 17:13
@xdustinface
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dash-spv/src/client/queries.rs`:
- Line 78: The tracing::warn!(message) call treats message as a structured
field; change the tracing::warn! invocation that references the variable message
to use a format string (e.g., tracing::warn!("{}", message)) or the formatter
shorthand (tracing::warn!(%message)) so the text is emitted as the event message
to match the tracing::debug(...) style; update the invocation where
tracing::warn! is called with the local variable message.

In `@dash/src/sml/masternode_list_engine/helpers.rs`:
- Around line 96-101: Remove the redundant QuorumHash import in the test module:
the tests module already brings QuorumHash into scope via use super::*, so
update the use crate::{BlockHash, QuorumHash}; line (inside #[cfg(test)] mod
tests) to only import BlockHash (e.g., use crate::BlockHash;) or otherwise omit
QuorumHash, leaving functions/structs that reference QuorumHash untouched;
reference the test module and the use statement near the top of helpers.rs to
locate and edit the import.
🪄 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: 3fa92136-d9bf-4610-a80f-1de727fbf6c4

📥 Commits

Reviewing files that changed from the base of the PR and between a132945 and db34dfc.

📒 Files selected for processing (3)
  • dash-spv-ffi/src/platform_integration.rs
  • dash-spv/src/client/queries.rs
  • dash/src/sml/masternode_list_engine/helpers.rs

height,
hex::encode(quorum_hash)
);
tracing::warn!(message);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent tracing macro usage.

tracing::warn!(message) treats message as a structured field name, producing output like message="Quorum not found: ..." rather than emitting the text as the event message. This differs from the tracing::debug!(...) call above which uses a format string.

Proposed fix
-                tracing::warn!(message);
+                tracing::warn!("{}", message);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tracing::warn!(message);
tracing::warn!("{}", message);
🤖 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 `@dash-spv/src/client/queries.rs` at line 78, The tracing::warn!(message) call
treats message as a structured field; change the tracing::warn! invocation that
references the variable message to use a format string (e.g.,
tracing::warn!("{}", message)) or the formatter shorthand
(tracing::warn!(%message)) so the text is emitted as the event message to match
the tracing::debug(...) style; update the invocation where tracing::warn! is
called with the local variable message.

Comment on lines +96 to +101
use super::*;
use crate::bls_sig_utils::{BLSPublicKey, BLSSignature};
use crate::hash_types::QuorumVVecHash;
use crate::sml::quorum_validation_error::QuorumValidationError;
use crate::transaction::special_transaction::quorum_commitment::QuorumEntry;
use crate::{BlockHash, QuorumHash};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does rustc emit an "imported redundantly" unused_imports warning when an item brought in by use super::*is also explicitly re-imported with an explicituse in the same module?

💡 Result:

Yes, the Rust compiler (rustc) can emit a warning when an item is imported redundantly [1][2]. Specifically, if an item is already brought into scope via a wildcard import like use super::*;, and you additionally import that same item explicitly with its own use statement, rustc may flag this as a "redundant import" [1][2]. In modern versions of rustc, this is typically handled by the redundant_imports lint [1][2]. The compiler detects that the item is already available in the current scope (e.g., from a previous import or the prelude) and marks the explicit re-import as redundant [1][2]. While this was previously grouped under the unused_imports lint, recent improvements have aimed to provide clearer diagnostics for redundant imports [3][4]. Note that this behavior is distinct from the unused_imports warning, which triggers when an import is entirely unused in the module [5][6]. If you use an item brought in by use super::*, that wildcard import itself might still be flagged as unused if the compiler cannot prove it provides any necessary items that aren't already covered by other imports, or if it is entirely ignored [6][3]. In practice, use super::* is often used in test modules, where it can sometimes trigger warnings if all imported items are already provided by other means [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="dash/src/sml/masternode_list_engine/helpers.rs"
echo "== file = $FILE"
wc -l "$FILE"

echo "== top of file (show module/parent imports)"
sed -n '1,140p' "$FILE" | nl -ba

echo "== targeted region (imports around lines 80-120)"
sed -n '70,130p' "$FILE" | nl -ba

Repository: dashpay/rust-dashcore

Length of output: 278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="dash/src/sml/masternode_list_engine/helpers.rs"

echo "== file = $FILE"
wc -l "$FILE"

echo "== targeted region (imports around ~70-130) with line numbers"
sed -n '70,130p' "$FILE" | awk '{printf "%4d\t%s\n", NR+69, $0}'

echo
echo "== top of file (first ~1-80) with line numbers"
sed -n '1,80p' "$FILE" | awk '{printf "%4d\t%s\n", NR, $0}'

echo
echo "== occurrences of QuorumHash in this file"
rg -n "QuorumHash" "$FILE" || true

Repository: dashpay/rust-dashcore

Length of output: 8447


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="dash/src/sml/masternode_list_engine/helpers.rs"
rg -n "use\s+crate::.*BlockHash|use\s+crate::\{[^}]*BlockHash|BlockHash" "$FILE" | head -n 80

Repository: dashpay/rust-dashcore

Length of output: 204


Fix redundant QuorumHash import in test module
In dash/src/sml/masternode_list_engine/helpers.rs (#[cfg(test)] mod tests, around lines 96-101), QuorumHash is already in scope via use super::*;, but it’s re-imported in use crate::{BlockHash, QuorumHash};, which can trip redundant_imports under -D warnings.

♻️ Proposed fix
-    use crate::{BlockHash, QuorumHash};
+    use crate::BlockHash;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use super::*;
use crate::bls_sig_utils::{BLSPublicKey, BLSSignature};
use crate::hash_types::QuorumVVecHash;
use crate::sml::quorum_validation_error::QuorumValidationError;
use crate::transaction::special_transaction::quorum_commitment::QuorumEntry;
use crate::{BlockHash, QuorumHash};
use super::*;
use crate::bls_sig_utils::{BLSPublicKey, BLSSignature};
use crate::hash_types::QuorumVVecHash;
use crate::sml::quorum_validation_error::QuorumValidationError;
use crate::transaction::special_transaction::quorum_commitment::QuorumEntry;
use crate::BlockHash;
🤖 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 `@dash/src/sml/masternode_list_engine/helpers.rs` around lines 96 - 101, Remove
the redundant QuorumHash import in the test module: the tests module already
brings QuorumHash into scope via use super::*, so update the use
crate::{BlockHash, QuorumHash}; line (inside #[cfg(test)] mod tests) to only
import BlockHash (e.g., use crate::BlockHash;) or otherwise omit QuorumHash,
leaving functions/structs that reference QuorumHash untouched; reference the
test module and the use statement near the top of helpers.rs to locate and edit
the import.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 82.43243% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.75%. Comparing base (a132945) to head (db34dfc).

Files with missing lines Patch % Lines
dash-spv-ffi/src/platform_integration.rs 0.00% 13 Missing ⚠️
dash-spv/src/client/queries.rs 0.00% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #801      +/-   ##
==========================================
+ Coverage   72.67%   72.75%   +0.07%     
==========================================
  Files         322      322              
  Lines       71363    71454      +91     
==========================================
+ Hits        51866    51986     +120     
+ Misses      19497    19468      -29     
Flag Coverage Δ
core 76.71% <100.00%> (+0.17%) ⬆️
ffi 46.30% <0.00%> (-0.11%) ⬇️
rpc 20.00% <ø> (ø)
spv 90.39% <0.00%> (+0.08%) ⬆️
wallet 71.29% <ø> (ø)
Files with missing lines Coverage Δ
dash/src/sml/masternode_list_engine/helpers.rs 100.00% <100.00%> (ø)
dash-spv-ffi/src/platform_integration.rs 27.47% <0.00%> (+4.74%) ⬆️
dash-spv/src/client/queries.rs 21.21% <0.00%> (+5.65%) ⬆️

... and 24 files with indirect coverage changes

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.

dash-spv: get_quorum_at_height fails for a just-retired Platform quorum (InvalidQuorum) though the pubkey is resident in quorum_statuses

1 participant