Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6ec639f
feat(dash): add env-gated QRInfo capture hook for offline repro
xdustinface Aug 9, 2026
4ab37e2
fix(dash): key rotation ChainLock signatures per quorum work block
xdustinface Aug 9, 2026
66a7edd
fix(dash): fall back to the block-hash quorum modifier for zeroed Cha…
xdustinface Aug 9, 2026
152d801
fix(dash): wrap the NoSkipping quarter fill around the combined list
xdustinface Aug 9, 2026
64dfab4
fix(dash): keep a corrupt previous cycle from aborting the whole QRIn…
xdustinface Aug 9, 2026
4b2e227
fix(dash): store the verified subset of a partly unverifiable previou…
xdustinface Aug 9, 2026
1ba2e4a
fix(dash): reject an out-of-range `quorum_index` instead of underflow…
xdustinface Aug 10, 2026
e543dad
fix(dash): degrade a quorum resting on an inferred ChainLock signature
xdustinface Aug 10, 2026
3c50405
docs(dash): drop the stale sig-alignment claim from the h work block …
xdustinface Aug 10, 2026
c57df94
refactor(dash): gate the QRInfo capture hook behind a non-default fea…
xdustinface Aug 10, 2026
2876db1
refactor(dash): share one diff list between `feed_qr_info` and its ha…
xdustinface Aug 10, 2026
d59823e
refactor(dash): take the rotation quorum type from the network, not t…
xdustinface Aug 10, 2026
13d635d
fix(dash): keep an unkeyable previous cycle from aborting the QRInfo …
xdustinface Aug 10, 2026
c8da0f3
fix(dash): let a complete rotation cycle replace a partially stored one
xdustinface Aug 10, 2026
bc790cb
feat(dash): report previous-cycle quorums dropped as invalid to the c…
xdustinface Aug 10, 2026
3af7bef
test(dash): tighten the rotated-quorum fixture tests
xdustinface Aug 10, 2026
17fb35d
test(dash): cover rotated quorum hashes outside the h diff in the has…
xdustinface Aug 10, 2026
7f7fd61
refactor(dash): import what the capture hook encodes with
xdustinface Aug 10, 2026
2c799fe
fix(dash): merge verified rotation entries into a stored cycle
xdustinface Aug 10, 2026
d4bae88
fix(dash): classify `CycleBaseHeightTooLow` as skipped, not invalid
xdustinface Aug 10, 2026
a81ba13
refactor(dash): give inferred rotation sigs their own skip status
xdustinface Aug 10, 2026
9e2b0e8
refactor(dash): compute the four quarter work heights in one place
xdustinface Aug 10, 2026
3299a0c
fix(dash): check the presumed quorum height for overflow
xdustinface Aug 10, 2026
abcc8ed
docs(dash): describe the per-quorum signature mechanism the diffs feed
xdustinface Aug 10, 2026
1b5a2f8
refactor(dash): tidy the rotation log strings and test block feeding
xdustinface Aug 10, 2026
9dd347f
fix(dash): require the full active set before reporting a cycle verified
xdustinface Aug 12, 2026
a38b625
fix(dash): report an active set whose cycle key cannot be resolved
xdustinface Aug 12, 2026
51e0981
fix(dash): reject a rotated quorum index that misplaces its cycle base
xdustinface Aug 12, 2026
b0be898
fix(dash): cache only a rotated cycle's successful member reconstruction
xdustinface Aug 12, 2026
a98de13
fix(dash): stop blaming a peer for an inferred previous-cycle signature
xdustinface Aug 12, 2026
0569730
fix(dash): let an unconfigured devnet take its rotation type from the…
xdustinface Aug 12, 2026
eeda22e
refactor(dash): key rotation signatures only when the feed reads them
xdustinface Aug 12, 2026
652a932
refactor(dash): resolve a single rotated quorum through the plural path
xdustinface Aug 12, 2026
3ea8b6e
refactor(dash): survey a rotated quorum's quarter heights in one place
xdustinface Aug 12, 2026
2abdfc8
docs(dash): state the extra heights a caller feeds without naming one
xdustinface Aug 12, 2026
b4412e8
test(dash): pin `SkipFirst` offsets as relative to the first skipped …
xdustinface Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dash-spv/src/sync/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,13 @@ impl fmt::Display for SyncEvent {
} => match qr_info_result {
Some(s) => write!(
f,
"MasternodeStateUpdated(height={}, qr_info={{stored_cycle_height={:?}, verified={}/{}, newly_qualified={}}})",
"MasternodeStateUpdated(height={}, qr_info={{stored_cycle_height={:?}, verified={}/{}, newly_qualified={}, previous_cycle_invalid={}}})",
height,
s.stored_cycle_height,
s.fully_verified_count,
s.rotated_quorum_count,
s.newly_qualified_count,
s.previous_cycle_invalid_count,
),
None => write!(f, "MasternodeStateUpdated(height={})", height),
},
Expand Down
5 changes: 4 additions & 1 deletion dash-spv/src/sync/masternodes/sync_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,14 @@ impl<H: BlockHeaderStorage> SyncManager for MasternodesManager<H> {

if let Some(ref qr_info_result) = qr_info_result {
tracing::info!(
"QRInfo processed: stored_cycle_height={:?}, rotated_quorum_count={}, fully_verified_count={}, newly_qualified_count={}",
"QRInfo processed: stored_cycle_height={:?}, rotated_quorum_count={}/{}, fully_verified_count={}, newly_qualified_count={}, cycle_key_unresolved={}, previous_cycle_invalid_count={}",
qr_info_result.stored_cycle_height,
qr_info_result.rotated_quorum_count,
qr_info_result.expected_rotated_quorum_count,
qr_info_result.fully_verified_count,
qr_info_result.newly_qualified_count,
qr_info_result.cycle_key_unresolved,
qr_info_result.previous_cycle_invalid_count,
);
// If every rotated quorum in this QRInfo ended up Verified,
// mark the cycle validated so `next_pipeline_mode` will
Expand Down
1 change: 1 addition & 0 deletions dash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ eddsa = ["ed25519-dalek"]
quorum_validation = ["bls"]
message_verification = ["bls"]
bincode = [ "dep:bincode", "dep:bincode_derive", "dashcore_hashes/bincode", "dash-network/bincode" ]
qrinfo-capture = ["bincode"]
test-utils = []

[package.metadata.docs.rs]
Expand Down
36 changes: 31 additions & 5 deletions dash/src/sml/llmq_entry_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ pub enum LLMQEntryVerificationSkipStatus {
/// fetch.
MissingChainLock(CoreBlockHeight, BlockHash),
/// The quorum entry came through without an attached
/// `VerifyingChainLockSignaturesType::Rotating`. Typically happens when
/// a QRInfo's historical diff covers a block range in which no rotating
/// DKG successfully committed, so `apply_diff` extracts no
/// `rotation_sig` and `feed_qr_info` can't populate the 4-sig tuple for
/// the quorums in `lastCommitmentPerIndex`.
/// `VerifyingChainLockSignaturesType::Rotating`. Each of the four quarter
/// signatures is looked up by the work height of its quarter in the
/// `quorumsCLSigs` mapping the QRInfo's diffs carry, so a QRInfo that
/// covers none of a quorum's quarter work blocks, or whose quorum hash
/// heights are unknown, leaves the 4-sig tuple unpopulated.
MissingRotationChainLockSigs(QuorumHash),
/// A specific rotation chain-lock signature at offset `h - n` was not
/// present for the masternode diff at the given block hash. The first
Expand All @@ -38,6 +38,12 @@ pub enum LLMQEntryVerificationSkipStatus {
/// where the entire 4-sig tuple is absent.
MissingRotationChainLockSig(u8, BlockHash),
OtherContext(String),
/// At least one of the quarter chain-lock signatures used to reconstruct
/// this quorum was keyed by elimination rather than by a known quorum
/// height, so a verification failure says nothing about the quorum data
/// itself. Retry logic can resolve it by feeding the heights of the
/// rotated quorum hashes the QRInfo carries.
InferredRotationChainLockSigs(QuorumHash),
}

impl Display for LLMQEntryVerificationSkipStatus {
Expand Down Expand Up @@ -71,6 +77,9 @@ impl Display for LLMQEntryVerificationSkipStatus {
LLMQEntryVerificationSkipStatus::OtherContext(message) => {
format!("OtherContext({message})")
}
LLMQEntryVerificationSkipStatus::InferredRotationChainLockSigs(quorum_hash) => {
format!("InferredRotationChainLockSigs({})", quorum_hash)
}
}
.as_str(),
)
Expand Down Expand Up @@ -122,6 +131,13 @@ impl From<QuorumValidationError> for LLMQEntryVerificationStatus {
offset, block_hash,
))
}
// A cycle base sitting below the depth a rotation reconstruction
// reaches back to means the quarter lists cannot exist yet. That
// is missing history like its siblings above, so it degrades the
// one quorum instead of rejecting everything served with it.
QuorumValidationError::CycleBaseHeightTooLow(height) => {
Self::Skipped(LLMQEntryVerificationSkipStatus::MissedList(height))
}
other => Self::Invalid(other),
}
}
Expand Down Expand Up @@ -208,6 +224,12 @@ mod tests {
LLMQEntryVerificationSkipStatus::MissingRotationChainLockSig(2, h4),
),
),
(
QuorumValidationError::CycleBaseHeightTooLow(5),
LLMQEntryVerificationStatus::Skipped(LLMQEntryVerificationSkipStatus::MissedList(
5,
)),
),
(
QuorumValidationError::InvalidQuorumPublicKey,
LLMQEntryVerificationStatus::Invalid(QuorumValidationError::InvalidQuorumPublicKey),
Expand Down Expand Up @@ -237,6 +259,10 @@ mod tests {
LLMQEntryVerificationSkipStatus::MissingRotationChainLockSig(2, h),
format!("MissingRotationChainLockSig(h - 2, {h})"),
),
(
LLMQEntryVerificationSkipStatus::InferredRotationChainLockSigs(h),
format!("InferredRotationChainLockSigs({h})"),
),
];

for (status, expected) in cases {
Expand Down
Loading
Loading