Skip to content

Commit 4d927c1

Browse files
bfoss765claude
andcommitted
feat(sml): masternodes_by_voting_key lookup on MasternodeList
Adds a by-voting-key lookup (returns matching pro_reg_tx_hashes) so SDK consumers can resolve which masternodes an imported voting key controls without the legacy dashj masternode list — needed for contested-username voting after the dashj engine is held post-cutover. Data was already present on every entry (key_id_voting, pro_reg_tx_hash); this is the missing index/helper plus a unit test covering multi-match, single-match and no-match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b3c216a commit 4d927c1

1 file changed

Lines changed: 84 additions & 1 deletion

File tree

dash/src/sml/masternode_list/masternode_helpers.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
11
use std::net::IpAddr;
22

3-
use crate::ProTxHash;
43
use crate::sml::masternode_list::MasternodeList;
4+
use crate::{ProTxHash, PubkeyHash};
55

66
impl MasternodeList {
7+
/// Every masternode in the list whose voting key hash matches
8+
/// `voting_key_id`, returned as their registration proTxHashes.
9+
///
10+
/// Mirrors dashj's `MasternodeList.getMasternodesByVotingKey(votingKeyId)`
11+
/// — the lookup contested-username voting uses to resolve which
12+
/// masternode(s) a given voting key is entitled to cast a vote for.
13+
/// `key_id_voting` is the 20-byte hash160 of the voting public key; a
14+
/// single voting key can back more than one masternode, so the result is
15+
/// a `Vec` (empty when no entry matches).
16+
pub fn masternodes_by_voting_key(&self, voting_key_id: &PubkeyHash) -> Vec<ProTxHash> {
17+
self.masternodes
18+
.values()
19+
.filter(|node| node.masternode_list_entry.key_id_voting == *voting_key_id)
20+
.map(|node| node.masternode_list_entry.pro_reg_tx_hash)
21+
.collect()
22+
}
23+
724
pub fn has_valid_masternode(&self, pro_reg_tx_hash: &ProTxHash) -> bool {
825
self.masternodes
926
.get(pro_reg_tx_hash)
@@ -46,3 +63,69 @@ pub fn reverse_cmp_sup(lhs: [u8; 32], rhs: [u8; 32]) -> bool {
4663
// equal
4764
false
4865
}
66+
67+
#[cfg(test)]
68+
mod tests {
69+
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
70+
71+
use hashes::Hash;
72+
73+
use crate::bls_sig_utils::BLSPublicKey;
74+
use crate::sml::masternode_list::MasternodeList;
75+
use crate::sml::masternode_list_entry::{
76+
EntryMasternodeType, MasternodeListEntry, MasternodeNetInfo,
77+
};
78+
use crate::{BlockHash, ProTxHash, PubkeyHash};
79+
80+
/// Build a `MasternodeList` from `(proTxHash-seed, voting-key-id)` pairs so
81+
/// each entry gets a distinct proTxHash and a caller-chosen voting key.
82+
fn list_from(entries: Vec<(u8, [u8; 20])>) -> MasternodeList {
83+
let masternodes = entries
84+
.into_iter()
85+
.map(|(seed, voting_key_id)| {
86+
let mut hash_bytes = [0u8; 32];
87+
hash_bytes[0] = seed;
88+
let pro_tx_hash = ProTxHash::from_byte_array(hash_bytes);
89+
let entry = MasternodeListEntry {
90+
version: 1,
91+
pro_reg_tx_hash: pro_tx_hash,
92+
confirmed_hash: None,
93+
service_address: MasternodeNetInfo::Legacy(SocketAddr::V4(SocketAddrV4::new(
94+
Ipv4Addr::new(10, 0, 0, seed),
95+
9999,
96+
))),
97+
operator_public_key: BLSPublicKey::from([0u8; 48]),
98+
key_id_voting: PubkeyHash::from_byte_array(voting_key_id),
99+
is_valid: true,
100+
mn_type: EntryMasternodeType::Regular,
101+
};
102+
(pro_tx_hash, entry.into())
103+
})
104+
.collect();
105+
MasternodeList::build(masternodes, Default::default(), BlockHash::from_byte_array([0u8; 32]), 0)
106+
.build()
107+
}
108+
109+
#[test]
110+
fn masternodes_by_voting_key_filters_and_collects() {
111+
let key_a = [0xAAu8; 20];
112+
let key_b = [0xBBu8; 20];
113+
// Two masternodes share voting key A, one uses key B.
114+
let list = list_from(vec![(1, key_a), (2, key_b), (3, key_a)]);
115+
116+
let mut matched = list.masternodes_by_voting_key(&PubkeyHash::from_byte_array(key_a));
117+
// Order is BTreeMap (proTxHash) order; sort the seed byte for a stable assert.
118+
matched.sort_by_key(|h| h.to_byte_array()[0]);
119+
assert_eq!(matched.len(), 2);
120+
assert_eq!(matched[0].to_byte_array()[0], 1);
121+
assert_eq!(matched[1].to_byte_array()[0], 3);
122+
123+
let single = list.masternodes_by_voting_key(&PubkeyHash::from_byte_array(key_b));
124+
assert_eq!(single.len(), 1);
125+
assert_eq!(single[0].to_byte_array()[0], 2);
126+
127+
// A voting key no masternode uses yields an empty vec.
128+
let none = list.masternodes_by_voting_key(&PubkeyHash::from_byte_array([0xCCu8; 20]));
129+
assert!(none.is_empty());
130+
}
131+
}

0 commit comments

Comments
 (0)