dusk_consensus/user/
provisioners.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use 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    /// Swap `self.current` and `self.prev` and update `self.current` with `new`
55    pub fn update_and_swap(&mut self, mut new: Provisioners) {
56        mem::swap(&mut self.current, &mut new);
57
58        // `new` has been swapped, and now hold the previous `self.current`
59        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    /// Change `self.current` with `new` and set `self.prev` to [None]
71    pub fn update(&mut self, new: Provisioners) {
72        self.current = new;
73        self.prev = None;
74    }
75
76    /// Derive the previous state of provisioners.
77    ///
78    /// This method takes a vector of tuples representing the previous state of
79    /// each provisioner. Each tuple consists of a `PublicKey` and an
80    /// optional `Stake`.
81    ///
82    /// If the `changes` vector is not empty, it iterates
83    /// over each change, deriving the previous state of provisioners from
84    /// the current state, and updates the current state accordingly.
85    ///
86    /// If the `changes` vector is empty, the previous state of the provisioners
87    /// is considered equal to the current
88    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    /// Adds a provisioner with stake.
110    ///
111    /// If the provisioner already exists, no action is performed.
112    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    /// Subtract `amount` from a staker, returning the stake left
132    ///
133    /// Return None if the entry was not found or `amount` is higher than
134    /// current stake
135    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    /// Adds a provisioner with a stake of value `value`
158    /// `reward` and `elibile_since` are set to 0.
159    ///
160    /// This function is used for unit tests.
161    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    // Returns a pair of count of all provisioners and count of eligible
170    // provisioners for the specified round.
171    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    /// Runs the deterministic sortition algorithm which determines the
187    /// committee members for a given round, step and seed.
188    ///
189    /// Returns the committee as a list of the extracted provisioners public
190    /// keys, where keys can have repetitions.
191    pub(crate) fn create_committee(
192        &self,
193        cfg: &sortition::Config,
194    ) -> Vec<PublicKey> {
195        let committee_credits = cfg.committee_credits();
196        // List of the extracted members.
197        // Note: members extracted multiple times will appear multiple times in
198        // the list
199        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            // Compute sortition hash
215            // hash = H(seed ∣∣ step ∣∣ index)
216            let hash = sortition::create_sortition_hash(cfg, credit_index);
217
218            // Compute sortition score
219            // score = hash % eligible_weight
220            let score =
221                sortition::generate_sortition_score(hash, &eligible_weight);
222
223            // Extract the committee member.
224            // Note: eligible provisioners can be extracted multiple times for
225            // the same committee
226            let (prov_pk, prov_weight) = comm_gen.extract_member(score);
227            // Add the extracted member to the committee
228            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    // Provisioners eligible for the committee
267    eligibles: BTreeMap<&'a PublicKey, Stake>,
268}
269
270impl<'a> CommitteeGenerator<'a> {
271    /// Creates a [`CommitteeGenerator`] from the provisioner set.
272    ///
273    /// # Arguments
274    /// * `provisioners` - the current list of provisioners
275    /// * `round` - the round of the extraction (to determine eligibility)
276    /// * `exclusion_list` - list of provisioners to exclude from extraction
277    fn new(
278        provisioners: &'a Provisioners,
279        round: u64,
280        exclusion_list: &[PublicKeyBytes],
281    ) -> Self {
282        // Get provisioners eligible at round `round`
283        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        // Set `eligibles` to  the eligible set minus the exclusion list
292        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            // If only one provisioner is eligible, we always include it
307            BTreeMap::from_iter(eligible_set)
308        };
309
310        Self { eligibles }
311    }
312
313    /// Sums up the total weight of all eligible provisioners
314    fn eligible_weight(&self) -> u64 {
315        self.eligibles.values().map(|m| m.value()).sum()
316    }
317
318    /// Extracts a member from `eligibles` given a Sortition score.
319    ///
320    /// At the beginning of the extraction, each provisioner has a weight equal
321    /// to its stake. Each time a provisioner is extracted, its weight is
322    /// reduced by 1 DUSK to decrease its probability of being extracted.
323    ///
324    /// # Arguments
325    /// * `score` - the Sortition score for the extraction
326    ///
327    /// # Output
328    /// * The extracted stake [`PublicKey`]
329    /// * The remaining stake weight after the extraction
330    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            // Loop over eligible provisioners
337            for (&provisioner, provisioner_weight) in self.eligibles.iter_mut()
338            {
339                // Set the initial provisioner's weight to the stake's value
340                let weight = BigInt::from(provisioner_weight.value());
341
342                // If the provisioner's weight is higher than the score, extract
343                // the provisioner and reduce its weight
344                if weight >= score {
345                    // Subtract 1 DUSK from the extracted stake's weight
346                    let new_weight =
347                        BigInt::from(provisioner_weight.subtract(DUSK));
348
349                    return (provisioner.clone(), new_weight);
350                }
351
352                // Otherwise, decrease the score and move to the next
353                // provisioner
354                score -= weight;
355            }
356        }
357    }
358}