diem_types/term_state/
lock_status.rs

1use std::{
2    collections::{vec_deque::Iter, VecDeque},
3    fmt::Debug,
4};
5
6use serde::{Deserialize, Serialize};
7
8#[cfg(any(test, feature = "fuzzing"))]
9use proptest_derive::Arbitrary;
10
11use diem_logger::prelude::*;
12
13use crate::{
14    block_info::View,
15    term_state::pos_state_config::{PosStateConfigTrait, POS_STATE_CONFIG},
16};
17
18#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
19#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
20pub struct StatusItem {
21    pub view: View,
22    pub votes: u64,
23}
24
25#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
26#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
27pub struct StatusList {
28    inner: VecDeque<StatusItem>,
29    sorted: bool,
30}
31
32impl Default for StatusList {
33    fn default() -> Self {
34        Self {
35            inner: VecDeque::new(),
36            sorted: true,
37        }
38    }
39}
40
41impl StatusList {
42    /// Push a given `StatusItem` into list and record it into `update_views`.
43    fn push(
44        &mut self, exit_view: View, votes: u64, update_views: &mut Vec<View>,
45    ) {
46        // If the pushed item breaks the ascending order of list, set
47        // `self.sorted` to false.
48        if self
49            .inner
50            .back()
51            .map_or(false, |item| item.view > exit_view)
52        {
53            self.sorted = false;
54        }
55        self.inner.push_back(StatusItem {
56            view: exit_view,
57            votes,
58        });
59        update_views.push(exit_view);
60    }
61
62    /// Pull the first item from list. If `votes` of the first item exceed
63    /// `required_votes`, the rest votes will be put back.
64    fn pull(&mut self, required_votes: u64) -> Option<StatusItem> {
65        self.sort();
66        if let Some(item) = self.inner.pop_front() {
67            if item.votes <= required_votes {
68                Some(item)
69            } else {
70                let rest_votes = item.votes - required_votes;
71                self.inner.push_front(StatusItem {
72                    view: item.view,
73                    votes: rest_votes,
74                });
75                Some(StatusItem {
76                    view: item.view,
77                    votes: required_votes,
78                })
79            }
80        } else {
81            None
82        }
83    }
84
85    /// Pop the first item if its view is no larger than given `view`.
86    fn pop_by_view(&mut self, view: View) -> Option<StatusItem> {
87        self.sort();
88        if let Some(item) = self.inner.pop_front() {
89            if item.view > view {
90                self.inner.push_front(item);
91                None
92            } else {
93                Some(item)
94            }
95        } else {
96            None
97        }
98    }
99
100    fn sort(&mut self) {
101        if !self.sorted {
102            self.inner
103                .make_contiguous()
104                .sort_unstable_by_key(|item| item.view);
105            self.sorted = true;
106        }
107    }
108
109    fn clear(&mut self) {
110        self.inner.clear();
111        self.sorted = true;
112    }
113
114    pub fn len(&self) -> usize { self.inner.len() }
115
116    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
117
118    pub fn iter(&self) -> Iter<'_, StatusItem> { self.inner.iter() }
119}
120
121#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Default)]
122#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
123pub struct NodeLockStatus {
124    pub in_queue: StatusList,
125    pub locked: u64,
126    pub out_queue: StatusList,
127    unlocked: u64,
128
129    // Equals to the summation of in_queue + locked
130    available_votes: u64,
131
132    // Record the view being forced retire.
133    force_retired: Option<View>,
134    // If the staking is forfeited, the unlocked votes before forfeiting is
135    // exempted.
136    exempt_from_forfeit: Option<u64>,
137}
138
139impl NodeLockStatus {
140    pub fn available_votes(&self) -> u64 {
141        if self.exempt_from_forfeit.is_some() {
142            0
143        } else {
144            self.available_votes
145        }
146    }
147
148    pub fn unlocked_votes(&self) -> u64 {
149        self.exempt_from_forfeit.unwrap_or(self.unlocked)
150    }
151
152    pub fn forfeited(&self) -> u64 { self.unlocked - self.unlocked_votes() }
153
154    pub fn force_retired(&self) -> Option<u64> { self.force_retired }
155
156    pub fn exempt_from_forfeit(&self) -> Option<u64> {
157        self.exempt_from_forfeit
158    }
159}
160
161impl NodeLockStatus {
162    pub(super) fn update(&mut self, view: View) -> bool {
163        let mut new_votes_unlocked = false;
164
165        while let Some(item) = self.in_queue.pop_by_view(view) {
166            self.locked += item.votes;
167        }
168
169        while let Some(item) = self.out_queue.pop_by_view(view) {
170            self.unlocked += item.votes;
171            new_votes_unlocked = true;
172        }
173
174        if self.force_retired.map_or(false, |retire_view| {
175            view >= retire_view
176                + POS_STATE_CONFIG.force_retired_locked_views(view)
177        }) {
178            self.force_retired = None;
179        }
180
181        if self.exempt_from_forfeit.is_some() {
182            new_votes_unlocked = false
183        }
184
185        new_votes_unlocked
186    }
187
188    pub(super) fn new_lock(
189        &mut self, view: View, votes: u64, initialize_mode: bool,
190        update_views: &mut Vec<View>,
191    ) {
192        if votes == 0 {
193            return;
194        }
195
196        if initialize_mode {
197            self.available_votes += votes;
198            self.locked += votes;
199            return;
200        }
201
202        // If force retired is not none, new locked tokens will be forced
203        // retire.
204        if self.force_retired.is_some() {
205            let exit_view = view
206                + POS_STATE_CONFIG.in_queue_locked_views(view)
207                + POS_STATE_CONFIG.out_queue_locked_views(view);
208            self.out_queue.push(exit_view, votes, update_views);
209        } else {
210            self.available_votes += votes;
211            let exit_view = view + POS_STATE_CONFIG.in_queue_locked_views(view);
212            self.in_queue.push(exit_view, votes, update_views);
213        }
214    }
215
216    pub(super) fn new_unlock(
217        &mut self, view: View, to_unlock_votes: u64,
218        update_views: &mut Vec<View>,
219    ) {
220        if to_unlock_votes == 0 {
221            return;
222        }
223
224        let before_available_votes = self.available_votes;
225        let mut rest_votes = to_unlock_votes;
226
227        // First, we try to unlock votes from self.locked
228        let votes = rest_votes.min(self.locked);
229        if votes > 0 {
230            rest_votes -= votes;
231            self.locked -= votes;
232            self.available_votes -= votes;
233
234            let exit_view =
235                view + POS_STATE_CONFIG.out_queue_locked_views(view);
236            self.out_queue.push(exit_view, votes, update_views);
237        }
238
239        // Then, we try to unlock votes from `in_queue`, ordered by timestamp.
240        while rest_votes > 0 {
241            let maybe_item = self.in_queue.pull(rest_votes);
242
243            if maybe_item.is_none() {
244                diem_warn!(
245                    "Not enough votes to unlock: before available votes {}, to unlock votes {}, rest votes {}.",
246                    before_available_votes,
247                    to_unlock_votes,
248                    rest_votes
249                );
250                break;
251            }
252
253            let item = maybe_item.unwrap();
254
255            rest_votes -= item.votes;
256            self.available_votes -= item.votes;
257
258            let exit_view =
259                item.view + POS_STATE_CONFIG.out_queue_locked_views(view);
260            self.out_queue.push(exit_view, item.votes, update_views);
261        }
262    }
263
264    pub(super) fn force_retire(
265        &mut self, view: View, callback_views: &mut Vec<View>,
266    ) {
267        if self.force_retired.is_none() {
268            self.force_retired = Some(view);
269            callback_views
270                .push(view + POS_STATE_CONFIG.force_retired_locked_views(view));
271            self.new_unlock(view, self.available_votes, callback_views);
272        }
273    }
274
275    pub(super) fn forfeit(
276        &mut self, view: View, updated_views: &mut Vec<View>,
277    ) {
278        if self.exempt_from_forfeit.is_some() {
279            return;
280        }
281        match POS_STATE_CONFIG.dispute_locked_views(view) {
282            None => self.exempt_from_forfeit = Some(self.unlocked),
283            Some(dispute_locked_views) => {
284                // available_votes excludes out_queue, so a fully-retired node
285                // (stake only in out_queue) escaped the lock before the fix.
286                let relock = self.available_votes > 0
287                    || (POS_STATE_CONFIG.dispute_lock_includes_out_queue(view)
288                        && !self.out_queue.is_empty());
289                if relock {
290                    // We will lock all votes in `in_queue`, `locked`, and
291                    // `out_queue`.
292                    let mut to_lock_votes = self.available_votes;
293                    self.in_queue.clear();
294                    self.locked = 0;
295                    self.available_votes = 0;
296
297                    while let Some(item) = self.out_queue.pop_by_view(u64::MAX)
298                    {
299                        to_lock_votes += item.votes;
300                    }
301                    // `out_queue` is cleared, so we also clear force_retired in
302                    // case it will not be updated in the
303                    // future.
304                    self.force_retired = None;
305
306                    self.out_queue.push(
307                        view.saturating_add(dispute_locked_views),
308                        to_lock_votes,
309                        updated_views,
310                    );
311                }
312            }
313        }
314    }
315}
316
317#[allow(dead_code)]
318pub mod tests {
319    use super::*;
320    use std::collections::HashSet;
321
322    enum Operation {
323        NewLock(u64),
324        NewUnlock(u64),
325        ForceRetire,
326        Forfeit,
327        AssertAvailable(u64),
328        AssertLocked(u64),
329        AssertUnlocked(u64),
330    }
331
332    use Operation::*;
333
334    fn run_tasks(tasks: Vec<(Operation, View)>) {
335        let mut tasks: VecDeque<(Operation, View)> = tasks.into();
336
337        let mut lock_status = NodeLockStatus::default();
338        let mut hint_views = HashSet::<View>::new();
339        let mut view = 0;
340
341        while !(tasks.is_empty() && hint_views.is_empty()) {
342            if hint_views.contains(&view) {
343                lock_status.update(view);
344                hint_views.remove(&view);
345            }
346
347            let mut update_views = Vec::new();
348
349            while tasks.front().map(|x| x.1) == Some(view) {
350                match tasks.pop_front().unwrap().0 {
351                    Operation::NewLock(votes) => {
352                        lock_status.new_lock(
353                            view,
354                            votes,
355                            false,
356                            &mut update_views,
357                        );
358                    }
359                    Operation::NewUnlock(votes) => {
360                        lock_status.new_unlock(view, votes, &mut update_views);
361                    }
362                    Operation::ForceRetire => {
363                        lock_status.force_retire(view, &mut update_views);
364                    }
365                    Operation::Forfeit => {
366                        lock_status.forfeit(view, &mut update_views)
367                    }
368                    Operation::AssertAvailable(votes) => {
369                        if lock_status.available_votes != votes {
370                            panic!("View {}\n {:?}", view, lock_status);
371                        }
372                    }
373                    Operation::AssertLocked(votes) => {
374                        if lock_status.locked != votes {
375                            panic!("View {}\n {:?}", view, lock_status);
376                        }
377                    }
378                    Operation::AssertUnlocked(votes) => {
379                        if lock_status.unlocked_votes() != votes {
380                            panic!("View {}\n {:?}", view, lock_status);
381                        }
382                    }
383                }
384            }
385
386            for update_view in update_views {
387                if update_view > view {
388                    hint_views.insert(update_view);
389                }
390            }
391            view += 1;
392        }
393    }
394
395    // #[test]
396    fn basic() {
397        let one_vote = vec![
398            (NewLock(1), 2),
399            (AssertAvailable(1), 3),
400            (AssertLocked(1), 10082),
401            (NewUnlock(1), 20000),
402            (AssertAvailable(0), 20001),
403            (AssertUnlocked(0), 20002),
404            (AssertUnlocked(1), 30080),
405        ];
406
407        let multi_vote = vec![
408            (NewLock(10), 2u64),
409            (AssertAvailable(10), 3),
410            (AssertLocked(10), 10082),
411            (NewUnlock(7), 20000),
412            (AssertAvailable(3), 20001),
413            (AssertUnlocked(0), 20002),
414            (AssertUnlocked(7), 30080),
415            (AssertAvailable(3), 30081),
416        ];
417
418        run_tasks(one_vote);
419        run_tasks(multi_vote);
420    }
421
422    // #[test]
423    fn increase_during_exit() {
424        let tasks = vec![
425            (NewLock(10), 2),
426            (AssertAvailable(10), 3),
427            (NewLock(5), 4),
428            (AssertAvailable(15), 5),
429            (NewUnlock(7), 20000),
430            (AssertAvailable(8), 20001),
431            (NewLock(5), 20002),
432            (AssertAvailable(13), 20003),
433            (NewUnlock(7), 20004),
434            (AssertAvailable(6), 20005),
435            (AssertUnlocked(0), 20005),
436            (NewUnlock(3), 20006),
437            (AssertUnlocked(7), 30080),
438            (AssertUnlocked(14), 30084),
439            (AssertUnlocked(15), 30086),
440            (AssertUnlocked(15), 40161),
441            (AssertUnlocked(17), 40162),
442        ];
443
444        run_tasks(tasks);
445    }
446
447    // #[test]
448    fn force_retire() {
449        let tasks = vec![
450            (NewLock(6), 2),
451            (AssertAvailable(6), 3),
452            (NewLock(7), 12),
453            (AssertAvailable(13), 13),
454            (AssertLocked(6), 10090),
455            (ForceRetire, 10090),
456            (NewLock(8), 10092),
457            (AssertAvailable(0), 10093),
458            (AssertLocked(0), 10093),
459            (AssertUnlocked(0), 20169),
460            (AssertUnlocked(6), 20170),
461            (NewLock(9), 20170),
462            (AssertAvailable(9), 20171),
463            (AssertUnlocked(13), 20172),
464            (AssertLocked(0), 30249),
465            (AssertLocked(9), 30250),
466            (AssertUnlocked(13), 30251),
467            (AssertUnlocked(21), 30252),
468        ];
469
470        run_tasks(tasks);
471    }
472
473    fn resolve_retired() {
474        let tasks = vec![
475            (NewLock(6), 2),
476            (AssertAvailable(6), 3),
477            (ForceRetire, 10),
478            (NewLock(8), 10090),
479            (AssertAvailable(8), 10091),
480        ];
481
482        run_tasks(tasks);
483    }
484
485    pub fn run_all() {
486        basic();
487        increase_during_exit();
488        force_retire();
489        resolve_retired();
490    }
491}