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 {
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    /// Swap `self.current` and `self.prev` and update `self.current` with `new`
57    pub fn update_and_swap(&mut self, mut new: Provisioners) {
58        mem::swap(&mut self.current, &mut new);
59
60        // `new` has been swapped, and now hold the previous `self.current`
61        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    /// Change `self.current` with `new` and set `self.prev` to [None]
73    pub fn update(&mut self, new: Provisioners) {
74        self.current = new;
75        self.prev = None;
76    }
77
78    /// Derive the previous state of provisioners.
79    ///
80    /// This method takes a vector of tuples representing the previous state of
81    /// each provisioner. Each tuple consists of a `PublicKey` and an
82    /// optional `Stake`.
83    ///
84    /// If the `changes` vector is not empty, it iterates
85    /// over each change, deriving the previous state of provisioners from
86    /// the current state, and updates the current state accordingly.
87    ///
88    /// If the `changes` vector is empty, the previous state of the provisioners
89    /// is considered equal to the current
90    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    /// Adds a provisioner with stake.
114    ///
115    /// If the provisioner already exists, no action is performed.
116    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    /// Subtract `amount` from a staker, returning the stake left
140    ///
141    /// Return None if the entry was not found or `amount` is higher than
142    /// current stake
143    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    /// Adds a new member with reward=0 and elibile_since=0.
166    ///
167    /// Useful for implementing unit tests.
168    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    // Returns a pair of count of all provisioners and count of eligible
173    // provisioners for the specified round.
174    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    /// Runs the deterministic sortition algorithm which determines the
190    /// committee members for a given round, step and seed.
191    ///
192    /// Returns a vector of provisioners public keys.
193    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            // 1. Compute n ← H(seed ∣∣ step ∣∣ counter)
213            let hash = sortition::create_sortition_hash(cfg, counter);
214
215            // 2. Compute d ← n mod s
216            let score =
217                sortition::generate_sortition_score(hash, &total_weight);
218
219            // NB: The public key can be extracted multiple times per committee.
220            let (pk, subtracted_stake) =
221                comm.extract_and_subtract_member(score);
222            // append the public key to the committee set.
223            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            // This is the edge case when there is only 1 active provisioner.
286            // Handling it just for single node cluster scenario
287            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    /// Sums up the total weight of all stakes
305    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                    // Subtract 1 DUSK from the value extracted and rebalance
322                    // accordingly.
323                    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}