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 iter(&self) -> Iter<'_, StatusItem> { self.inner.iter() }
117}
118
119#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Default)]
120#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
121pub struct NodeLockStatus {
122    pub in_queue: StatusList,
123    pub locked: u64,
124    pub out_queue: StatusList,
125    unlocked: u64,
126
127    // Equals to the summation of in_queue + locked
128    available_votes: u64,
129
130    // Record the view being forced retire.
131    force_retired: Option<View>,
132    // If the staking is forfeited, the unlocked votes before forfeiting is
133    // exempted.
134    exempt_from_forfeit: Option<u64>,
135}
136
137impl NodeLockStatus {
138    pub fn available_votes(&self) -> u64 {
139        if self.exempt_from_forfeit.is_some() {
140            0
141        } else {
142            self.available_votes
143        }
144    }
145
146    pub fn unlocked_votes(&self) -> u64 {
147        self.exempt_from_forfeit.unwrap_or(self.unlocked)
148    }
149
150    pub fn forfeited(&self) -> u64 { self.unlocked - self.unlocked_votes() }
151
152    pub fn force_retired(&self) -> Option<u64> { self.force_retired }
153
154    pub fn exempt_from_forfeit(&self) -> Option<u64> {
155        self.exempt_from_forfeit
156    }
157}
158
159impl NodeLockStatus {
160    pub(super) fn update(&mut self, view: View) -> bool {
161        let mut new_votes_unlocked = false;
162
163        while let Some(item) = self.in_queue.pop_by_view(view) {
164            self.locked += item.votes;
165        }
166
167        while let Some(item) = self.out_queue.pop_by_view(view) {
168            self.unlocked += item.votes;
169            new_votes_unlocked = true;
170        }
171
172        if self.force_retired.map_or(false, |retire_view| {
173            view >= retire_view
174                + POS_STATE_CONFIG.force_retired_locked_views(view)
175        }) {
176            self.force_retired = None;
177        }
178
179        if self.exempt_from_forfeit.is_some() {
180            new_votes_unlocked = false
181        }
182
183        new_votes_unlocked
184    }
185
186    pub(super) fn new_lock(
187        &mut self, view: View, votes: u64, initialize_mode: bool,
188        update_views: &mut Vec<View>,
189    ) {
190        if votes == 0 {
191            return;
192        }
193
194        if initialize_mode {
195            self.available_votes += votes;
196            self.locked += votes;
197            return;
198        }
199
200        // If force retired is not none, new locked tokens will be forced
201        // retire.
202        if self.force_retired.is_some() {
203            let exit_view = view
204                + POS_STATE_CONFIG.in_queue_locked_views(view)
205                + POS_STATE_CONFIG.out_queue_locked_views(view);
206            self.out_queue.push(exit_view, votes, update_views);
207        } else {
208            self.available_votes += votes;
209            let exit_view = view + POS_STATE_CONFIG.in_queue_locked_views(view);
210            self.in_queue.push(exit_view, votes, update_views);
211        }
212    }
213
214    pub(super) fn new_unlock(
215        &mut self, view: View, to_unlock_votes: u64,
216        update_views: &mut Vec<View>,
217    ) {
218        if to_unlock_votes == 0 {
219            return;
220        }
221
222        let before_available_votes = self.available_votes;
223        let mut rest_votes = to_unlock_votes;
224
225        // First, we try to unlock votes from self.locked
226        let votes = rest_votes.min(self.locked);
227        if votes > 0 {
228            rest_votes -= votes;
229            self.locked -= votes;
230            self.available_votes -= votes;
231
232            let exit_view =
233                view + POS_STATE_CONFIG.out_queue_locked_views(view);
234            self.out_queue.push(exit_view, votes, update_views);
235        }
236
237        // Then, we try to unlock votes from `in_queue`, ordered by timestamp.
238        while rest_votes > 0 {
239            let maybe_item = self.in_queue.pull(rest_votes);
240
241            if maybe_item.is_none() {
242                diem_warn!(
243                    "Not enough votes to unlock: before available votes {}, to unlock votes {}, rest votes {}.",
244                    before_available_votes,
245                    to_unlock_votes,
246                    rest_votes
247                );
248                break;
249            }
250
251            let item = maybe_item.unwrap();
252
253            rest_votes -= item.votes;
254            self.available_votes -= item.votes;
255
256            let exit_view =
257                item.view + POS_STATE_CONFIG.out_queue_locked_views(view);
258            self.out_queue.push(exit_view, item.votes, update_views);
259        }
260    }
261
262    pub(super) fn force_retire(
263        &mut self, view: View, callback_views: &mut Vec<View>,
264    ) {
265        if self.force_retired.is_none() {
266            self.force_retired = Some(view);
267            callback_views
268                .push(view + POS_STATE_CONFIG.force_retired_locked_views(view));
269            self.new_unlock(view, self.available_votes, callback_views);
270        }
271    }
272
273    pub(super) fn forfeit(
274        &mut self, view: View, updated_views: &mut Vec<View>,
275    ) {
276        if self.exempt_from_forfeit.is_some() {
277            return;
278        }
279        match POS_STATE_CONFIG.dispute_locked_views(view) {
280            None => self.exempt_from_forfeit = Some(self.unlocked),
281            Some(dispute_locked_views) => {
282                if self.available_votes > 0 {
283                    // We will lock all votes in `in_queue`, `locked`, and
284                    // `out_queue`.
285                    let mut to_lock_votes = self.available_votes;
286                    self.in_queue.clear();
287                    self.locked = 0;
288                    self.available_votes = 0;
289
290                    while let Some(item) = self.out_queue.pop_by_view(u64::MAX)
291                    {
292                        to_lock_votes += item.votes;
293                    }
294                    // `out_queue` is cleared, so we also clear force_retired in
295                    // case it will not be updated in the
296                    // future.
297                    self.force_retired = None;
298
299                    self.out_queue.push(
300                        view.saturating_add(dispute_locked_views),
301                        to_lock_votes,
302                        updated_views,
303                    );
304                }
305            }
306        }
307    }
308}
309
310#[allow(dead_code)]
311pub mod tests {
312    use super::*;
313    use std::collections::HashSet;
314
315    enum Operation {
316        NewLock(u64),
317        NewUnlock(u64),
318        ForceRetire,
319        Forfeit,
320        AssertAvailable(u64),
321        AssertLocked(u64),
322        AssertUnlocked(u64),
323    }
324
325    use Operation::*;
326
327    fn run_tasks(tasks: Vec<(Operation, View)>) {
328        let mut tasks: VecDeque<(Operation, View)> = tasks.into();
329
330        let mut lock_status = NodeLockStatus::default();
331        let mut hint_views = HashSet::<View>::new();
332        let mut view = 0;
333
334        while !(tasks.is_empty() && hint_views.is_empty()) {
335            if hint_views.contains(&view) {
336                lock_status.update(view);
337                hint_views.remove(&view);
338            }
339
340            let mut update_views = Vec::new();
341
342            while tasks.front().map(|x| x.1) == Some(view) {
343                match tasks.pop_front().unwrap().0 {
344                    Operation::NewLock(votes) => {
345                        lock_status.new_lock(
346                            view,
347                            votes,
348                            false,
349                            &mut update_views,
350                        );
351                    }
352                    Operation::NewUnlock(votes) => {
353                        lock_status.new_unlock(view, votes, &mut update_views);
354                    }
355                    Operation::ForceRetire => {
356                        lock_status.force_retire(view, &mut update_views);
357                    }
358                    Operation::Forfeit => {
359                        lock_status.forfeit(view, &mut update_views)
360                    }
361                    Operation::AssertAvailable(votes) => {
362                        if lock_status.available_votes != votes {
363                            panic!("View {}\n {:?}", view, lock_status);
364                        }
365                    }
366                    Operation::AssertLocked(votes) => {
367                        if lock_status.locked != votes {
368                            panic!("View {}\n {:?}", view, lock_status);
369                        }
370                    }
371                    Operation::AssertUnlocked(votes) => {
372                        if lock_status.unlocked_votes() != votes {
373                            panic!("View {}\n {:?}", view, lock_status);
374                        }
375                    }
376                }
377            }
378
379            for update_view in update_views {
380                if update_view > view {
381                    hint_views.insert(update_view);
382                }
383            }
384            view += 1;
385        }
386    }
387
388    // #[test]
389    fn basic() {
390        let one_vote = vec![
391            (NewLock(1), 2),
392            (AssertAvailable(1), 3),
393            (AssertLocked(1), 10082),
394            (NewUnlock(1), 20000),
395            (AssertAvailable(0), 20001),
396            (AssertUnlocked(0), 20002),
397            (AssertUnlocked(1), 30080),
398        ];
399
400        let multi_vote = vec![
401            (NewLock(10), 2u64),
402            (AssertAvailable(10), 3),
403            (AssertLocked(10), 10082),
404            (NewUnlock(7), 20000),
405            (AssertAvailable(3), 20001),
406            (AssertUnlocked(0), 20002),
407            (AssertUnlocked(7), 30080),
408            (AssertAvailable(3), 30081),
409        ];
410
411        run_tasks(one_vote);
412        run_tasks(multi_vote);
413    }
414
415    // #[test]
416    fn increase_during_exit() {
417        let tasks = vec![
418            (NewLock(10), 2),
419            (AssertAvailable(10), 3),
420            (NewLock(5), 4),
421            (AssertAvailable(15), 5),
422            (NewUnlock(7), 20000),
423            (AssertAvailable(8), 20001),
424            (NewLock(5), 20002),
425            (AssertAvailable(13), 20003),
426            (NewUnlock(7), 20004),
427            (AssertAvailable(6), 20005),
428            (AssertUnlocked(0), 20005),
429            (NewUnlock(3), 20006),
430            (AssertUnlocked(7), 30080),
431            (AssertUnlocked(14), 30084),
432            (AssertUnlocked(15), 30086),
433            (AssertUnlocked(15), 40161),
434            (AssertUnlocked(17), 40162),
435        ];
436
437        run_tasks(tasks);
438    }
439
440    // #[test]
441    fn force_retire() {
442        let tasks = vec![
443            (NewLock(6), 2),
444            (AssertAvailable(6), 3),
445            (NewLock(7), 12),
446            (AssertAvailable(13), 13),
447            (AssertLocked(6), 10090),
448            (ForceRetire, 10090),
449            (NewLock(8), 10092),
450            (AssertAvailable(0), 10093),
451            (AssertLocked(0), 10093),
452            (AssertUnlocked(0), 20169),
453            (AssertUnlocked(6), 20170),
454            (NewLock(9), 20170),
455            (AssertAvailable(9), 20171),
456            (AssertUnlocked(13), 20172),
457            (AssertLocked(0), 30249),
458            (AssertLocked(9), 30250),
459            (AssertUnlocked(13), 30251),
460            (AssertUnlocked(21), 30252),
461        ];
462
463        run_tasks(tasks);
464    }
465
466    fn resolve_retired() {
467        let tasks = vec![
468            (NewLock(6), 2),
469            (AssertAvailable(6), 3),
470            (ForceRetire, 10),
471            (NewLock(8), 10090),
472            (AssertAvailable(8), 10091),
473        ];
474
475        run_tasks(tasks);
476    }
477
478    pub fn run_all() {
479        basic();
480        increase_during_exit();
481        force_retire();
482        resolve_retired();
483    }
484}