1use node_data::StepName;
8use node_data::bls::PublicKeyBytes;
9use node_data::ledger::Seed;
10use num_bigint::BigInt;
11use num_bigint::Sign::Plus;
12use sha3::{Digest, Sha3_256};
13
14use crate::config::{
15 PROPOSAL_COMMITTEE_CREDITS, RATIFICATION_COMMITTEE_CREDITS,
16 VALIDATION_COMMITTEE_CREDITS,
17};
18
19#[derive(Debug, Clone, Default, Eq, Hash, PartialEq)]
20pub struct Config {
21 seed: Seed,
22 round: u64,
23 pub step: u8,
24 committee_credits: usize,
25 exclusion: Vec<PublicKeyBytes>,
26}
27
28impl Config {
29 pub fn new(
30 seed: Seed,
31 round: u64,
32 iteration: u8,
33 step: StepName,
34 exclusion: Vec<PublicKeyBytes>,
35 ) -> Config {
36 let committee_credits = match step {
37 StepName::Proposal => PROPOSAL_COMMITTEE_CREDITS,
38 StepName::Ratification => RATIFICATION_COMMITTEE_CREDITS,
39 StepName::Validation => VALIDATION_COMMITTEE_CREDITS,
40 };
41 let step = step.to_step(iteration);
42 Self {
43 seed,
44 round,
45 step,
46 committee_credits,
47 exclusion,
48 }
49 }
50
51 pub fn committee_credits(&self) -> usize {
52 self.committee_credits
53 }
54
55 pub fn step(&self) -> u8 {
56 self.step
57 }
58
59 pub fn round(&self) -> u64 {
60 self.round
61 }
62
63 pub fn exclusion(&self) -> &Vec<PublicKeyBytes> {
64 &self.exclusion
65 }
66}
67
68pub fn create_sortition_hash(cfg: &Config, counter: u32) -> [u8; 32] {
73 let mut hasher = Sha3_256::new();
74
75 hasher.update(&cfg.seed.inner()[..]);
77 hasher.update(cfg.step.to_le_bytes());
78 hasher.update(counter.to_le_bytes());
79
80 let reader = hasher.finalize();
82 reader.as_slice().try_into().expect("Wrong length")
83}
84
85pub fn generate_sortition_score(
87 hash: [u8; 32],
88 total_weight: &BigInt,
89) -> BigInt {
90 let num = BigInt::from_bytes_be(Plus, hash.as_slice());
91 num % total_weight
92}
93
94#[cfg(test)]
95mod tests {
96
97 use dusk_bytes::DeserializableSlice;
98 use dusk_core::signatures::bls::{
99 PublicKey as BlsPublicKey, SecretKey as BlsSecretKey,
100 };
101 use node_data::ledger::Seed;
102
103 use super::*;
104 use crate::user::committee::Committee;
105 use crate::user::provisioners::{DUSK, Provisioners};
106 use crate::user::sortition::Config;
107
108 impl Config {
109 pub fn raw(
110 seed: Seed,
111 round: u64,
112 step: u8,
113 committee_credits: usize,
114 exclusion: Vec<PublicKeyBytes>,
115 ) -> Config {
116 Self {
117 seed,
118 round,
119 step,
120 committee_credits,
121 exclusion,
122 }
123 }
124 }
125
126 #[test]
127 pub fn test_sortition_hash() {
128 let hash = [
129 74, 64, 238, 174, 226, 52, 11, 105, 93, 251, 204, 6, 137, 176, 14,
130 96, 77, 139, 92, 76, 7, 178, 38, 16, 132, 233, 13, 180, 78, 206,
131 204, 31,
132 ];
133
134 assert_eq!(
135 create_sortition_hash(
136 &Config::raw(Seed::from([3; 48]), 10, 3, 0, vec![]),
137 1
138 )[..],
139 hash[..],
140 );
141 }
142
143 #[test]
144 pub fn test_generate_sortition_score() {
145 let dataset = vec![
146 ([3; 48], 123342342, 80689917),
147 ([4; 48], 44443333, 20330495),
148 ];
149
150 for (seed, total_weight, expected_score) in dataset {
151 let hash = create_sortition_hash(
152 &Config::raw(Seed::from(seed), 10, 3, 0, vec![]),
153 1,
154 );
155
156 let total_weight = BigInt::from(total_weight);
157 let res = generate_sortition_score(hash, &total_weight);
158
159 assert_eq!(res, BigInt::from(expected_score));
160 }
161 }
162
163 #[test]
164 fn test_deterministic_sortition_1() {
165 let p = generate_provisioners(5);
166
167 let committee_credits = 64;
168
169 let cfg = Config::raw(Seed::default(), 1, 1, 64, vec![]);
171
172 let committee = Committee::new(&p, &cfg);
173
174 assert_eq!(
176 committee_credits,
177 committee.get_occurrences().iter().sum::<usize>()
178 );
179
180 assert_eq!(vec![4, 29, 9, 22], committee.get_occurrences());
182 }
183
184 #[test]
185 fn test_deterministic_sortition_2() {
186 let p = generate_provisioners(5);
187
188 let committee_credits = 45;
189 let cfg = Config::raw(
190 Seed::from([3u8; 48]),
191 7777,
192 8,
193 committee_credits,
194 vec![],
195 );
196
197 let committee = Committee::new(&p, &cfg);
198 assert_eq!(
199 committee_credits,
200 committee.get_occurrences().iter().sum::<usize>()
201 );
202 assert_eq!(vec![6, 13, 11, 15], committee.get_occurrences());
203 }
204
205 #[test]
206 fn test_deterministic_sortition_2_exclusion() {
207 let p = generate_provisioners(5);
208
209 let seed = Seed::from([3u8; 48]);
210 let round = 7777;
211 let committee_credits = 45;
212 let iteration = 2;
213 let relative_step = 2;
214 let step = iteration * 3 + relative_step;
215
216 let cfg = Config::raw(seed, round, step, committee_credits, vec![]);
217 let generator = p.get_generator(iteration, seed, round);
218 let committee = Committee::new(&p, &cfg);
219
220 committee
221 .iter()
222 .find(|&p| p.bytes() == &generator)
223 .expect("Generator to be included");
224 assert_eq!(
225 committee_credits,
226 committee.get_occurrences().iter().sum::<usize>()
227 );
228 assert_eq!(vec![6, 13, 11, 15], committee.get_occurrences());
229
230 let cfg =
232 Config::raw(seed, round, step, committee_credits, vec![generator]);
233 let committee = Committee::new(&p, &cfg);
234
235 assert!(
236 committee
237 .iter()
238 .find(|&p| p.bytes() == &generator)
239 .is_none(),
240 "Generator to be excluded"
241 );
242 assert_eq!(
243 committee_credits,
244 committee.get_occurrences().iter().sum::<usize>()
245 );
246 assert_eq!(vec![8, 13, 24], committee.get_occurrences());
247 }
248
249 #[test]
250 fn test_quorum() {
251 let p = generate_provisioners(5);
252
253 let cfg = Config::raw(Seed::default(), 7777, 8, 64, vec![]);
254
255 let c = Committee::new(&p, &cfg);
256 assert_eq!(c.super_majority_quorum(), 43);
257 }
258
259 #[test]
260 fn test_intersect() {
261 let p = generate_provisioners(10);
262
263 let cfg = Config::raw(Seed::default(), 1, 3, 200, vec![]);
264 let c = Committee::new(&p, &cfg);
267 let max_bitset = (2_i32.pow((c.size()) as u32) - 1) as u64;
270 println!("max_bitset: {} / {:#064b} ", max_bitset, max_bitset);
271
272 for bitset in 0..max_bitset {
273 let result = c.intersect(bitset);
275 assert_eq!(
276 c.bits(&result),
277 bitset,
278 "testing with bitset:{}",
279 bitset
280 );
281 }
282 }
283
284 fn generate_provisioners(n: usize) -> Provisioners {
285 let sks = [
286 "7f6f2ccdb23f2abb7b69278e947c01c6160a31cf02c19d06d0f6e5ab1d768b15",
287 "611830d3641a68f94a690dcc25d1f4b0dac948325ac18f6dd32564371735f32c",
288 "1fbec814b18b1d4c3eaa7cec41007e04bf0a98453b06ec7582aa29882c52eb3e",
289 "ecd9c4a53ea15f18447b08fb96a13c5ab7dc7d24067b102fcbaaf7b39ca52e2d",
290 "e463bcb1a6e57288ffd4671503082fa8656e3eacb78fb1925f8a7c76400e8e15",
291 "7a19fb2d099a9557f7c10c2efbb8b101d9e0ec85610d5c74a887d1d4fb8d2827",
292 "4dbad51eb408af559dd91bbbed8dbeae0a2c89e0e05f0cce87c98652a8437f1f",
293 "befba86ae9e0c207865f7e24e8349d4ecdbc8b0f4632842499a0dfa60568e20a",
294 "b260b8a10343bf5a5dacb4f1d32d06c4fdddc9981a3619fbc0a5cd9eb30f3334",
295 "87a9779748888da5d96bbbce041b5109c6ffc0c4f30561c0170384a5922d9e21",
296 ];
297 let sks: Vec<_> = sks
298 .iter()
299 .take(n)
300 .map(|hex| hex::decode(hex).expect("valid hex"))
301 .map(|data| {
302 BlsSecretKey::from_slice(&data[..]).expect("valid secret key")
303 })
304 .collect();
305
306 let mut p = Provisioners::empty();
307 for (i, sk) in sks.iter().enumerate().skip(1) {
308 let stake_value = 1000 * (i) as u64 * DUSK;
309 let stake_pk =
310 node_data::bls::PublicKey::new(BlsPublicKey::from(sk));
311 p.add_provisioner_with_value(stake_pk, stake_value);
312 }
313 p
314 }
315}