openmina_node_invariants/
lib.rs

1mod invariant_result;
2pub use invariant_result::{InvariantIgnoreReason, InvariantResult};
3
4pub mod no_recursion;
5use no_recursion::*;
6
7pub mod p2p;
8use p2p::*;
9
10pub mod transition_frontier;
11use transition_frontier::*;
12
13pub use node::core::invariants::{InvariantService, InvariantsState};
14
15use strum_macros::{EnumDiscriminants, EnumIter, EnumString, IntoStaticStr};
16
17use node::{ActionKind, ActionWithMeta, Service, Store};
18
19pub trait Invariant {
20    /// Internal state of the invariant.
21    ///
22    /// If some state needs to be preserved across checks,
23    /// this is the place.
24    type InternalState: 'static + Send + Default;
25
26    /// Whether or not invariant is cluster-wide, or for just local node.
27    fn is_global(&self) -> bool {
28        false
29    }
30
31    /// Invariant triggers define a list actions, which should cause
32    /// `Invariant::check` to be called.
33    ///
34    /// If empty, an invariant will never be checked!
35    fn triggers(&self) -> &[ActionKind];
36
37    /// Checks the state for invariant violation.
38    fn check<S: Service>(
39        self,
40        internal_state: &mut Self::InternalState,
41        store: &Store<S>,
42        action: &ActionWithMeta,
43    ) -> InvariantResult;
44}
45
46macro_rules! define_invariants_enum {
47    ($($invariant: ident,)+) => {
48        #[derive(EnumIter, EnumString, IntoStaticStr, EnumDiscriminants, Clone, Copy)]
49        #[strum(serialize_all = "snake_case")]
50        pub enum Invariants {
51            $($invariant($invariant),)*
52        }
53
54        impl Invariants {
55            pub fn index(self) -> usize {
56                InvariantsDiscriminants::from(self) as usize
57            }
58
59            pub fn is_global(&self) -> bool {
60                match self {
61                    $(Self::$invariant(invariant) => invariant.is_global(),)*
62                }
63            }
64
65            pub fn triggers(&self) -> &[ActionKind] {
66                match self {
67                    $(Self::$invariant(invariant) => invariant.triggers(),)*
68                }
69            }
70
71            pub fn check<S: Service + InvariantService>(
72                self,
73                store: &mut Store<S>,
74                action: &ActionWithMeta,
75            ) -> InvariantResult {
76                let mut invariants_state = if self.is_global() {
77                    match store.service.cluster_invariants_state() {
78                        Some(mut v) => v.take(),
79                        None => return InvariantResult::Ignored(InvariantIgnoreReason::GlobalInvariantNotInTestingCluster),
80                    }
81                } else {
82                    store.service.invariants_state().take()
83                };
84
85                let res = match self {
86                    $(Self::$invariant(invariant) => {
87                        let invariant_state = invariants_state.get(self.index());
88                        invariant.check(invariant_state, store, action)
89                    })*
90                };
91
92                if self.is_global() {
93                    match store.service.cluster_invariants_state() {
94                        Some(mut s) =>
95                        *s = invariants_state,
96                        None => unreachable!("function should have returned above"),
97                    }
98                } else {
99                    *store.service.invariants_state() = invariants_state;
100                };
101
102                res
103            }
104        }
105    };
106}
107
108define_invariants_enum! {
109    NoRecursion,
110    P2pStatesAreConsistent,
111    TransitionFrontierOnlySyncsToBetterBlocks,
112}
113
114lazy_static::lazy_static! {
115    /// List of invariants that need to be triggered if we see a given `ActionKind`.
116    static ref INVARIANTS_BY_ACTION_KIND: Vec<Vec<Invariants>> = {
117        let mut by_action_kind = Vec::new();
118        by_action_kind.resize_with(ActionKind::COUNT as usize, Vec::new);
119        for invariant in Invariants::iter() {
120            for action_kind in invariant.triggers() {
121                let v = by_action_kind.get_mut(*action_kind as usize).unwrap();
122                v.push(invariant);
123            }
124        }
125        by_action_kind
126    };
127}
128
129impl Invariants {
130    pub fn iter() -> impl Iterator<Item = Invariants> {
131        <Self as strum::IntoEnumIterator>::iter()
132    }
133
134    pub fn check_all<'a, S: Service + InvariantService>(
135        store: &'a mut Store<S>,
136        action: &'a ActionWithMeta,
137    ) -> impl 'a + Iterator<Item = (Self, InvariantResult)> {
138        let action_kind = action.action().kind();
139        INVARIANTS_BY_ACTION_KIND
140            .get(action_kind as usize)
141            .unwrap()
142            .iter()
143            .map(|invariant| (*invariant, invariant.check(store, action)))
144    }
145
146    pub fn to_str(self) -> &'static str {
147        self.into()
148    }
149}