dusk_consensus/user/
provisioners.rs1use std::collections::BTreeMap;
8use std::mem;
9
10use dusk_core::dusk;
11use dusk_core::stake::DEFAULT_MINIMUM_STAKE;
12use node_data::bls::{PublicKey, PublicKeyBytes};
13use node_data::ledger::Seed;
14use node_data::StepName;
15use num_bigint::BigInt;
16
17use super::committee::Committee;
18use crate::user::sortition;
19use crate::user::stake::Stake;
20
21pub const DUSK: u64 = dusk(1.0);
22
23#[derive(Clone, Debug)]
24pub struct Provisioners(BTreeMap<PublicKey, Stake>);
25
26impl Provisioners {
27 pub fn iter(&self) -> impl Iterator<Item = (&PublicKey, &Stake)> {
28 self.0.iter()
29 }
30}
31
32#[derive(Clone, Debug)]
33pub struct ContextProvisioners {
34 current: Provisioners,
35 prev: Option<Provisioners>,
36}
37
38impl ContextProvisioners {
39 pub fn new(current: Provisioners) -> Self {
40 Self {
41 current,
42 prev: None,
43 }
44 }
45 pub fn current(&self) -> &Provisioners {
46 &self.current
47 }
48 pub fn to_current(&self) -> Provisioners {
49 self.current.clone()
50 }
51 pub fn prev(&self) -> &Provisioners {
52 self.prev.as_ref().unwrap_or(&self.current)
53 }
54 pub fn update_and_swap(&mut self, mut new: Provisioners) {
56 mem::swap(&mut self.current, &mut new);
57
58 self.prev = Some(new);
60 }
61
62 pub fn remove_previous(&mut self) {
63 self.prev = None;
64 }
65
66 pub fn set_previous(&mut self, prev: Provisioners) {
67 self.prev = Some(prev);
68 }
69
70 pub fn update(&mut self, new: Provisioners) {
72 self.current = new;
73 self.prev = None;
74 }
75
76 pub fn apply_changes(&mut self, changes: Vec<(PublicKey, Option<Stake>)>) {
89 if !changes.is_empty() {
90 let mut prev = self.to_current();
91 for change in changes {
92 match change {
93 (pk, None) => prev.remove_stake(&pk),
94 (pk, Some(stake)) => prev.replace_stake(pk, stake),
95 };
96 }
97 self.set_previous(prev)
98 } else {
99 self.remove_previous()
100 }
101 }
102}
103
104impl Provisioners {
105 pub fn empty() -> Self {
106 Self(BTreeMap::default())
107 }
108
109 pub fn add_provisioner(&mut self, pubkey_bls: PublicKey, stake: Stake) {
113 self.0.entry(pubkey_bls).or_insert_with(|| stake);
114 }
115
116 pub fn get_provisioner_mut(
117 &mut self,
118 pubkey_bls: &PublicKey,
119 ) -> Option<&mut Stake> {
120 self.0.get_mut(pubkey_bls)
121 }
122
123 pub fn replace_stake(
124 &mut self,
125 pubkey_bls: PublicKey,
126 stake: Stake,
127 ) -> Option<Stake> {
128 self.0.insert(pubkey_bls, stake)
129 }
130
131 pub fn sub_stake(
136 &mut self,
137 pubkey_bls: &PublicKey,
138 amount: u64,
139 ) -> Option<u64> {
140 let stake = self.0.get_mut(pubkey_bls)?;
141 if stake.value() < amount {
142 None
143 } else {
144 stake.subtract(amount);
145 let left = stake.value();
146 if left == 0 {
147 self.0.remove(pubkey_bls);
148 }
149 Some(left)
150 }
151 }
152
153 pub fn remove_stake(&mut self, pubkey_bls: &PublicKey) -> Option<Stake> {
154 self.0.remove(pubkey_bls)
155 }
156
157 pub fn add_provisioner_with_value(
162 &mut self,
163 pubkey_bls: PublicKey,
164 value: u64,
165 ) {
166 self.add_provisioner(pubkey_bls, Stake::from_value(value));
167 }
168
169 pub fn get_provisioners_info(&self, round: u64) -> (usize, usize) {
172 let eligible_len = self.eligibles(round).count();
173
174 (self.0.len(), eligible_len)
175 }
176
177 pub fn eligibles(
178 &self,
179 round: u64,
180 ) -> impl Iterator<Item = (&PublicKey, &Stake)> {
181 self.0.iter().filter(move |(_, m)| {
182 m.is_eligible(round) && m.value() >= DEFAULT_MINIMUM_STAKE
183 })
184 }
185
186 pub(crate) fn create_committee(
192 &self,
193 cfg: &sortition::Config,
194 ) -> Vec<PublicKey> {
195 let committee_credits = cfg.committee_credits();
196 let mut committee: Vec<PublicKey> =
200 Vec::with_capacity(committee_credits);
201
202 let mut comm_gen =
203 CommitteeGenerator::new(self, cfg.round(), cfg.exclusion());
204
205 let mut eligible_weight = comm_gen.eligible_weight().into();
206
207 if eligible_weight == BigInt::ZERO {
208 panic!("No stakes available. Cannot run consensus");
209 }
210
211 while committee.len() != committee_credits {
212 let credit_index = committee.len() as u32;
213
214 let hash = sortition::create_sortition_hash(cfg, credit_index);
217
218 let score =
221 sortition::generate_sortition_score(hash, &eligible_weight);
222
223 let (prov_pk, prov_weight) = comm_gen.extract_member(score);
227 committee.push(prov_pk);
229
230 if eligible_weight > prov_weight {
231 eligible_weight -= prov_weight;
232 } else {
233 break;
234 }
235 }
236
237 committee
238 }
239
240 pub fn get_generator(
241 &self,
242 iteration: u8,
243 seed: Seed,
244 round: u64,
245 ) -> PublicKeyBytes {
246 let cfg = sortition::Config::new(
247 seed,
248 round,
249 iteration,
250 StepName::Proposal,
251 vec![],
252 );
253 let committee_keys = Committee::new(self, &cfg);
254
255 let generator = *committee_keys
256 .iter()
257 .next()
258 .expect("committee to have 1 entry")
259 .bytes();
260 generator
261 }
262}
263
264#[derive(Default)]
265struct CommitteeGenerator<'a> {
266 eligibles: BTreeMap<&'a PublicKey, Stake>,
268}
269
270impl<'a> CommitteeGenerator<'a> {
271 fn new(
278 provisioners: &'a Provisioners,
279 round: u64,
280 exclusion_list: &[PublicKeyBytes],
281 ) -> Self {
282 let eligible_set: Vec<_> = provisioners
284 .eligibles(round)
285 .map(|(pk, stake)| (pk, stake.clone()))
286 .collect();
287
288 let num_eligibles = eligible_set.len();
289 let eligible_set = eligible_set.into_iter();
290
291 let eligibles = if num_eligibles > 1 {
293 let eligible_iter = eligible_set;
294 match exclusion_list.len() {
295 0 => BTreeMap::from_iter(eligible_iter),
296 _ => {
297 let filtered_eligibles = eligible_iter.filter(|(p, _)| {
298 !exclusion_list
299 .iter()
300 .any(|excluded| excluded == p.bytes())
301 });
302 BTreeMap::from_iter(filtered_eligibles)
303 }
304 }
305 } else {
306 BTreeMap::from_iter(eligible_set)
308 };
309
310 Self { eligibles }
311 }
312
313 fn eligible_weight(&self) -> u64 {
315 self.eligibles.values().map(|m| m.value()).sum()
316 }
317
318 fn extract_member(&mut self, mut score: BigInt) -> (PublicKey, BigInt) {
331 if self.eligibles.is_empty() {
332 panic!("No eligible provisioners to extract for the committee");
333 }
334
335 loop {
336 for (&provisioner, provisioner_weight) in self.eligibles.iter_mut()
338 {
339 let weight = BigInt::from(provisioner_weight.value());
341
342 if weight >= score {
345 let new_weight =
347 BigInt::from(provisioner_weight.subtract(DUSK));
348
349 return (provisioner.clone(), new_weight);
350 }
351
352 score -= weight;
355 }
356 }
357 }
358}