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        dispute_lock_until: Option<View>, 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        // Removal for inactivity and a dispute penalty both bar topping up
203        // into active voting power, so the deposit queues for withdrawal.
204        if self.force_retired.is_some() || dispute_lock_until.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            .max(dispute_lock_until.unwrap_or(0));
209            self.out_queue.push(exit_view, votes, update_views);
210        } else {
211            self.available_votes += votes;
212            let exit_view = view + POS_STATE_CONFIG.in_queue_locked_views(view);
213            self.in_queue.push(exit_view, votes, update_views);
214        }
215    }
216
217    pub(super) fn new_unlock(
218        &mut self, view: View, to_unlock_votes: u64,
219        update_views: &mut Vec<View>,
220    ) {
221        if to_unlock_votes == 0 {
222            return;
223        }
224
225        let before_available_votes = self.available_votes;
226        let mut rest_votes = to_unlock_votes;
227
228        // First, we try to unlock votes from self.locked
229        let votes = rest_votes.min(self.locked);
230        if votes > 0 {
231            rest_votes -= votes;
232            self.locked -= votes;
233            self.available_votes -= votes;
234
235            let exit_view =
236                view + POS_STATE_CONFIG.out_queue_locked_views(view);
237            self.out_queue.push(exit_view, votes, update_views);
238        }
239
240        // Then, we try to unlock votes from `in_queue`, ordered by timestamp.
241        while rest_votes > 0 {
242            let maybe_item = self.in_queue.pull(rest_votes);
243
244            if maybe_item.is_none() {
245                diem_warn!(
246                    "Not enough votes to unlock: before available votes {}, to unlock votes {}, rest votes {}.",
247                    before_available_votes,
248                    to_unlock_votes,
249                    rest_votes
250                );
251                break;
252            }
253
254            let item = maybe_item.unwrap();
255
256            rest_votes -= item.votes;
257            self.available_votes -= item.votes;
258
259            let exit_view =
260                item.view + POS_STATE_CONFIG.out_queue_locked_views(view);
261            self.out_queue.push(exit_view, item.votes, update_views);
262        }
263    }
264
265    pub(super) fn force_retire(
266        &mut self, view: View, callback_views: &mut Vec<View>,
267    ) {
268        if self.force_retired.is_none() {
269            self.force_retired = Some(view);
270            callback_views
271                .push(view + POS_STATE_CONFIG.force_retired_locked_views(view));
272            self.new_unlock(view, self.available_votes, callback_views);
273        }
274    }
275
276    pub(super) fn forfeit(
277        &mut self, rule: ForfeitRule, updated_views: &mut Vec<View>,
278    ) {
279        if self.exempt_from_forfeit.is_some() {
280            return;
281        }
282        match rule {
283            ForfeitRule::FreezeWithdrawable => {
284                self.exempt_from_forfeit = Some(self.unlocked)
285            }
286            ForfeitRule::RelockOnActive { deadline } => {
287                // `available_votes` excludes `out_queue`, so a fully retired
288                // node escapes the lock. That is what CIP-173 fixes; it must
289                // stay exact to replay history written before the fix.
290                if self.available_votes > 0 {
291                    let mut to_lock_votes = self.available_votes;
292                    self.in_queue.clear();
293                    self.locked = 0;
294                    self.available_votes = 0;
295
296                    while let Some(item) = self.out_queue.pop_by_view(u64::MAX)
297                    {
298                        to_lock_votes += item.votes;
299                    }
300                    self.force_retired = None;
301
302                    self.out_queue.push(deadline, to_lock_votes, updated_views);
303                }
304            }
305            ForfeitRule::RelockAll { deadline } => {
306                self.relock_all(deadline, updated_views)
307            }
308        }
309    }
310
311    /// Holds every piece of stake to `max(its own exit view, deadline)`.
312    /// Flooring, not replacing: a dispute filed late would otherwise land
313    /// behind a withdrawal in flight and *shorten* its delay.
314    fn relock_all(&mut self, deadline: View, updated_views: &mut Vec<View>) {
315        // `pop_by_view` sorts first and flooring preserves that order, so the
316        // items can go back without a re-sort. Active stake has no exit view
317        // of its own, so it leads at the deadline.
318        let mut held = Vec::with_capacity(self.out_queue.len() + 1);
319        if self.available_votes > 0 {
320            held.push(StatusItem {
321                view: deadline,
322                votes: self.available_votes,
323            });
324        }
325        while let Some(item) = self.out_queue.pop_by_view(u64::MAX) {
326            held.push(StatusItem {
327                view: item.view.max(deadline),
328                votes: item.votes,
329            });
330        }
331
332        // Unlike the legacy rule this leaves `force_retired` alone: a penalty
333        // must not double as an amnesty for an ongoing retirement.
334        self.in_queue.clear();
335        self.locked = 0;
336        self.available_votes = 0;
337
338        for item in held {
339            self.out_queue.push(item.view, item.votes, updated_views);
340        }
341    }
342}
343
344/// Chosen by `PosState`, which has the transition views and dispute record.
345pub(super) enum ForfeitRule {
346    /// Before CIP-156: forfeit outright rather than lock.
347    FreezeWithdrawable,
348    /// CIP-156: relock, but only for a node that still has active stake.
349    RelockOnActive { deadline: View },
350    /// CIP-173: relock unconditionally, stake already leaving included.
351    RelockAll { deadline: View },
352}
353
354#[cfg(test)]
355mod relock_tests {
356    use super::*;
357
358    /// `relock_all` reads no config, so these cases need no `POS_STATE_CONFIG`.
359    fn status_with(out_queue: &[(View, u64)], active: u64) -> NodeLockStatus {
360        let mut status = NodeLockStatus::default();
361        status.available_votes = active;
362        status.locked = active;
363        for (view, votes) in out_queue {
364            status.out_queue.push(*view, *votes, &mut Vec::new());
365        }
366        status
367    }
368
369    #[test]
370    fn relocking_orders_the_queue_and_never_brings_an_exit_forward() {
371        // Pushed descending, so the order must come from `pop_by_view`.
372        let mut status = status_with(&[(900, 3), (100, 1), (500, 2)], 10);
373        status.force_retired = Some(7);
374        assert!(!status.out_queue.sorted);
375
376        let mut updated = Vec::new();
377        status.forfeit(ForfeitRule::RelockAll { deadline: 500 }, &mut updated);
378
379        let exits: Vec<(View, u64)> = status
380            .out_queue
381            .iter()
382            .map(|item| (item.view, item.votes))
383            .collect();
384        // The later exit survives; the earlier two are held to the deadline.
385        assert_eq!(exits, vec![(500, 10), (500, 1), (500, 2), (900, 3)]);
386        assert!(status.out_queue.sorted);
387        assert_eq!(status.available_votes, 0);
388        assert!(status.in_queue.is_empty());
389        assert_eq!(status.force_retired, Some(7));
390        assert_eq!(updated, vec![500, 500, 500, 900]);
391    }
392}
393
394#[allow(dead_code)]
395pub mod tests {
396    use super::*;
397    use std::collections::HashSet;
398
399    enum Operation {
400        NewLock(u64),
401        NewUnlock(u64),
402        ForceRetire,
403        AssertAvailable(u64),
404        AssertLocked(u64),
405        AssertUnlocked(u64),
406    }
407
408    use Operation::*;
409
410    fn run_tasks(tasks: Vec<(Operation, View)>) {
411        let mut tasks: VecDeque<(Operation, View)> = tasks.into();
412
413        let mut lock_status = NodeLockStatus::default();
414        let mut hint_views = HashSet::<View>::new();
415        let mut view = 0;
416
417        while !(tasks.is_empty() && hint_views.is_empty()) {
418            if hint_views.contains(&view) {
419                lock_status.update(view);
420                hint_views.remove(&view);
421            }
422
423            let mut update_views = Vec::new();
424
425            while tasks.front().map(|x| x.1) == Some(view) {
426                match tasks.pop_front().unwrap().0 {
427                    Operation::NewLock(votes) => {
428                        lock_status.new_lock(
429                            view,
430                            votes,
431                            false,
432                            None,
433                            &mut update_views,
434                        );
435                    }
436                    Operation::NewUnlock(votes) => {
437                        lock_status.new_unlock(view, votes, &mut update_views);
438                    }
439                    Operation::ForceRetire => {
440                        lock_status.force_retire(view, &mut update_views);
441                    }
442                    Operation::AssertAvailable(votes) => {
443                        if lock_status.available_votes != votes {
444                            panic!("View {}\n {:?}", view, lock_status);
445                        }
446                    }
447                    Operation::AssertLocked(votes) => {
448                        if lock_status.locked != votes {
449                            panic!("View {}\n {:?}", view, lock_status);
450                        }
451                    }
452                    Operation::AssertUnlocked(votes) => {
453                        if lock_status.unlocked_votes() != votes {
454                            panic!("View {}\n {:?}", view, lock_status);
455                        }
456                    }
457                }
458            }
459
460            for update_view in update_views {
461                if update_view > view {
462                    hint_views.insert(update_view);
463                }
464            }
465            view += 1;
466        }
467    }
468
469    // #[test]
470    fn basic() {
471        let one_vote = vec![
472            (NewLock(1), 2),
473            (AssertAvailable(1), 3),
474            (AssertLocked(1), 10082),
475            (NewUnlock(1), 20000),
476            (AssertAvailable(0), 20001),
477            (AssertUnlocked(0), 20002),
478            (AssertUnlocked(1), 30080),
479        ];
480
481        let multi_vote = vec![
482            (NewLock(10), 2u64),
483            (AssertAvailable(10), 3),
484            (AssertLocked(10), 10082),
485            (NewUnlock(7), 20000),
486            (AssertAvailable(3), 20001),
487            (AssertUnlocked(0), 20002),
488            (AssertUnlocked(7), 30080),
489            (AssertAvailable(3), 30081),
490        ];
491
492        run_tasks(one_vote);
493        run_tasks(multi_vote);
494    }
495
496    // #[test]
497    fn increase_during_exit() {
498        let tasks = vec![
499            (NewLock(10), 2),
500            (AssertAvailable(10), 3),
501            (NewLock(5), 4),
502            (AssertAvailable(15), 5),
503            (NewUnlock(7), 20000),
504            (AssertAvailable(8), 20001),
505            (NewLock(5), 20002),
506            (AssertAvailable(13), 20003),
507            (NewUnlock(7), 20004),
508            (AssertAvailable(6), 20005),
509            (AssertUnlocked(0), 20005),
510            (NewUnlock(3), 20006),
511            (AssertUnlocked(7), 30080),
512            (AssertUnlocked(14), 30084),
513            (AssertUnlocked(15), 30086),
514            (AssertUnlocked(15), 40161),
515            (AssertUnlocked(17), 40162),
516        ];
517
518        run_tasks(tasks);
519    }
520
521    // #[test]
522    fn force_retire() {
523        let tasks = vec![
524            (NewLock(6), 2),
525            (AssertAvailable(6), 3),
526            (NewLock(7), 12),
527            (AssertAvailable(13), 13),
528            (AssertLocked(6), 10090),
529            (ForceRetire, 10090),
530            (NewLock(8), 10092),
531            (AssertAvailable(0), 10093),
532            (AssertLocked(0), 10093),
533            (AssertUnlocked(0), 20169),
534            (AssertUnlocked(6), 20170),
535            (NewLock(9), 20170),
536            (AssertAvailable(9), 20171),
537            (AssertUnlocked(13), 20172),
538            (AssertLocked(0), 30249),
539            (AssertLocked(9), 30250),
540            (AssertUnlocked(13), 30251),
541            (AssertUnlocked(21), 30252),
542        ];
543
544        run_tasks(tasks);
545    }
546
547    fn resolve_retired() {
548        let tasks = vec![
549            (NewLock(6), 2),
550            (AssertAvailable(6), 3),
551            (ForceRetire, 10),
552            (NewLock(8), 10090),
553            (AssertAvailable(8), 10091),
554        ];
555
556        run_tasks(tasks);
557    }
558
559    pub fn run_all() {
560        basic();
561        increase_during_exit();
562        force_retire();
563        resolve_retired();
564    }
565}