Repository navigation
Description
Since v20 (DIP-0029), the members of a quorum are chosen with a modifier built from the ChainLock signature in the coinbase of the quorum's work block. When that coinbase carries no ChainLock, the modifier uses the work block's hash instead. Dash Core reads the signature with GetNonNullCoinbaseChainlock (src/evo/cbtx.cpp) and builds the modifier in GetHashModifierFromWorkBlock (src/llmq/utils.cpp). An mnlistdiff repeats the same signature for each new quorum in quorumsCLSigs, and sends an all-zero signature when the coinbase has none (CSimplifiedMNListDiff::BuildQuorumChainlockInfo, src/evo/smldiff.cpp).
dash-spv uses the quorumsCLSigs signature as it arrives:
MasternodeList::apply_diffandMasternodeList::from_diffstore it on each new non-rotating quorum (dash/src/sml/masternode_list/apply_diff.rs:92-130,dash/src/sml/masternode_list/from_diff.rs:48-89).validate_quorumpasses it toLLMQModifierType::new_quorum_modifier_type(dash-spv/src/sml_engine/validation.rs:34-48). That function switches to the block-hash modifier when the signature is all zeros (dash/src/sml/quorum_entry/quorum_modifier_type.rs:103-114).- For rotated quorums,
rotation_cl_sigs_by_work_heightkeys the QRInfo diffs'quorumsCLSigsto work blocks (dash-spv/src/sml_engine.rs:647-732), and the quarter signatures feed member selection the same way.
Nothing checks the signature against the work block. Since #1200, the quorum commitments themselves are bound to their block: the diff's coinbase is proven against the header, and the list must hash to its merkleRootQuorums (dash-spv/src/validation/masternode_list.rs:29-58). quorumsCLSigs, however, isn't part of any coinbase commitment.
So with a different signature, or an all-zero one in place of a real one, dash-spv selects a different member set. A commitment that is valid on chain then fails its signature check and is marked Invalid. Later lists copy that status onto the same quorum (dash-spv/src/sml_engine.rs:1077-1096), and re-validation uses the same stored signature. A ChainLock is only accepted from a Verified quorum (dash-spv/src/validation/chainlock.rs:77), so ChainLocks from that quorum are refused.
Proposed fix: take the signature from the work block's coinbase, as Dash Core does, instead of from quorumsCLSigs.
- The engine already applies a diff ending at each work block it validates against: the work-block lists for non-rotating quorums, and the QRInfo quarter diffs for rotated ones. That diff's coinbase is proven against the stored header.
- Its
CbTx(version 3 or later) holdsbest_cl_signature. - Following
GetNonNullCoinbaseChainlock, the signature counts only when the payload's height matches the block, the signature is valid andbestCLHeightDiffis below the height. Otherwise the modifier uses the work block's hash. quorumsCLSigscan then be checked against it, or dropped from member selection.
Open design point: where to keep the work block's signature between applying its diff and validating a quorum. It could be a field on MasternodeList, which changes the persisted snapshot format, or a height-keyed map in the engine that is pruned with the lists.
Test: a non-rotating quorum whose quorumsCLSigs entry differs from its work block's coinbase signature. A real commitment, captured together with its work block's diff, must still verify when the diff carries a wrong signature or the zero signature. That fails on dev today.
Activity
fix(dash-spv): take the non-rotating member-selection ChainLock from …
fix(dash-spv): take the non-rotating member-selection ChainLock from …