openmina_node_invariants/
lib.rs1mod 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 type InternalState: 'static + Send + Default;
25
26 fn is_global(&self) -> bool {
28 false
29 }
30
31 fn triggers(&self) -> &[ActionKind];
36
37 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 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}