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 {
25 members: BTreeMap<PublicKey, Stake>,
26}
27
28impl Provisioners {
29 pub fn iter(&self) -> impl Iterator<Item = (&PublicKey, &Stake)> {
30 self.members.iter()
31 }
32}
33
34#[derive(Clone, Debug)]
35pub struct ContextProvisioners {
36 current: Provisioners,
37 prev: Option<Provisioners>,
38}
39
40impl ContextProvisioners {
41 pub fn new(current: Provisioners) -> Self {
42 Self {
43 current,
44 prev: None,
45 }
46 }
47 pub fn current(&self) -> &Provisioners {
48 &self.current
49 }
50 pub fn to_current(&self) -> Provisioners {
51 self.current.clone()
52 }
53 pub fn prev(&self) -> &Provisioners {
54 self.prev.as_ref().unwrap_or(&self.current)
55 }
56 pub fn update_and_swap(&mut self, mut new: Provisioners) {
58 mem::swap(&mut self.current, &mut new);
59
60 self.prev = Some(new);
62 }
63
64 pub fn remove_previous(&mut self) {
65 self.prev = None;
66 }
67
68 pub fn set_previous(&mut self, prev: Provisioners) {
69 self.prev = Some(prev);
70 }
71
72 pub fn update(&mut self, new: Provisioners) {
74 self.current = new;
75 self.prev = None;
76 }
77
78 pub fn apply_changes(&mut self, changes: Vec<(PublicKey, Option<Stake>)>) {
91 if !changes.is_empty() {
92 let mut prev = self.to_current();
93 for change in changes {
94 match change {
95 (pk, None) => prev.remove_stake(&pk),
96 (pk, Some(stake)) => prev.replace_stake(pk, stake),
97 };
98 }
99 self.set_previous(prev)
100 } else {
101 self.remove_previous()
102 }
103 }
104}
105
106impl Provisioners {
107 pub fn empty() -> Self {
108 Self {
109 members: BTreeMap::default(),
110 }
111 }
112
113 pub fn add_member_with_stake(
117 &mut self,
118 pubkey_bls: PublicKey,
119 stake: Stake,
120 ) {
121 self.members.entry(pubkey_bls).or_insert_with(|| stake);
122 }
123
124 pub fn get_member_mut(
125 &mut self,
126 pubkey_bls: &PublicKey,
127 ) -> Option<&mut Stake> {
128 self.members.get_mut(pubkey_bls)
129 }
130
131 pub fn replace_stake(
132 &mut self,
133 pubkey_bls: PublicKey,
134 stake: Stake,
135 ) -> Option<Stake> {
136 self.members.insert(pubkey_bls, stake)
137 }
138
139 pub fn sub_stake(
144 &mut self,
145 pubkey_bls: &PublicKey,
146 amount: u64,
147 ) -> Option<u64> {
148 let stake = self.members.get_mut(pubkey_bls)?;
149 if stake.value() < amount {
150 None
151 } else {
152 stake.subtract(amount);
153 let left = stake.value();
154 if left == 0 {
155 self.members.remove(pubkey_bls);
156 }
157 Some(left)
158 }
159 }
160
161 pub fn remove_stake(&mut self, pubkey_bls: &PublicKey) -> Option<Stake> {
162 self.members.remove(pubkey_bls)
163 }
164
165 pub fn add_member_with_value(&mut self, pubkey_bls: PublicKey, value: u64) {
169 self.add_member_with_stake(pubkey_bls, Stake::from_value(value));
170 }
171
172 pub fn get_provisioners_info(&self, round: u64) -> (usize, usize) {
175 let eligible_len = self.eligibles(round).count();
176
177 (self.members.len(), eligible_len)
178 }
179
180 pub fn eligibles(
181 &self,
182 round: u64,
183 ) -> impl Iterator<Item = (&PublicKey, &Stake)> {
184 self.members.iter().filter(move |(_, m)| {
185 m.is_eligible(round) && m.value() >= DEFAULT_MINIMUM_STAKE
186 })
187 }
188
189 pub(crate) fn create_committee(
194 &self,
195 cfg: &sortition::Config,
196 ) -> Vec<PublicKey> {
197 let committee_credits = cfg.committee_credits();
198 let mut extracted: Vec<PublicKey> =
199 Vec::with_capacity(committee_credits);
200
201 let mut comm = CommitteeGenerator::from_provisioners(
202 self,
203 cfg.round(),
204 cfg.exclusion(),
205 );
206
207 let mut total_weight = comm.total_weight().into();
208
209 while extracted.len() != committee_credits {
210 let counter = extracted.len() as u32;
211
212 let hash = sortition::create_sortition_hash(cfg, counter);
214
215 let score =
217 sortition::generate_sortition_score(hash, &total_weight);
218
219 let (pk, subtracted_stake) =
221 comm.extract_and_subtract_member(score);
222 extracted.push(pk);
224
225 if total_weight > subtracted_stake {
226 total_weight -= subtracted_stake;
227 } else {
228 break;
229 }
230 }
231
232 extracted
233 }
234
235 pub fn get_generator(
236 &self,
237 iteration: u8,
238 seed: Seed,
239 round: u64,
240 ) -> PublicKeyBytes {
241 let cfg = sortition::Config::new(
242 seed,
243 round,
244 iteration,
245 StepName::Proposal,
246 vec![],
247 );
248 let committee_keys = Committee::new(self, &cfg);
249
250 let generator = *committee_keys
251 .iter()
252 .next()
253 .expect("committee to have 1 entry")
254 .bytes();
255 generator
256 }
257}
258
259#[derive(Default)]
260struct CommitteeGenerator<'a> {
261 members: BTreeMap<&'a PublicKey, Stake>,
262}
263
264impl<'a> CommitteeGenerator<'a> {
265 fn from_provisioners(
266 provisioners: &'a Provisioners,
267 round: u64,
268 exclusion: &Vec<PublicKeyBytes>,
269 ) -> Self {
270 let eligibles = provisioners
271 .eligibles(round)
272 .map(|(p, stake)| (p, stake.clone()));
273
274 let members = match exclusion.len() {
275 0 => BTreeMap::from_iter(eligibles),
276 _ => {
277 let eligibles = eligibles.filter(|(p, _)| {
278 !exclusion.iter().any(|excluded| excluded == p.bytes())
279 });
280 BTreeMap::from_iter(eligibles)
281 }
282 };
283
284 if members.is_empty() {
285 let eligibles = provisioners
288 .eligibles(round)
289 .map(|(p, stake)| (p, stake.clone()));
290
291 let members = BTreeMap::from_iter(eligibles);
292
293 debug_assert!(
294 !members.is_empty(),
295 "No provisioners are eligible for the committee"
296 );
297
298 Self { members }
299 } else {
300 Self { members }
301 }
302 }
303
304 fn total_weight(&self) -> u64 {
306 self.members.values().map(|m| m.value()).sum()
307 }
308
309 fn extract_and_subtract_member(
310 &mut self,
311 mut score: BigInt,
312 ) -> (PublicKey, BigInt) {
313 if self.members.is_empty() {
314 panic!("Cannot extract member from an empty committee");
315 }
316
317 loop {
318 for (&pk, stake) in self.members.iter_mut() {
319 let total_stake = BigInt::from(stake.value());
320 if total_stake >= score {
321 let subtracted_stake = BigInt::from(stake.subtract(DUSK));
324
325 return (pk.clone(), subtracted_stake);
326 }
327
328 score -= total_stake;
329 }
330 }
331 }
332}