1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
use core::panic;

use massa_consensus_exports::block_status::{BlockStatus, BlockStatusId};
use massa_models::{
    block_id::BlockId,
    prehash::{PreHashMap, PreHashSet},
    slot::Slot,
};

#[derive(Debug, Clone)]
pub struct BlocksState {
    /// Every block we know about
    block_statuses: PreHashMap<BlockId, BlockStatus>,
    /// Ids of incoming blocks/headers
    incoming_index: PreHashSet<BlockId>,
    /// Used to limit the number of waiting and discarded blocks
    sequence_counter: u64,
    /// ids of waiting for slot blocks/headers
    waiting_for_slot_index: PreHashSet<BlockId>,
    /// ids of waiting for dependencies blocks/headers
    waiting_for_dependencies_index: PreHashSet<BlockId>,
    /// ids of discarded blocks
    discarded_index: PreHashSet<BlockId>,
    /// ids of active blocks
    active_index: PreHashSet<BlockId>,
}

impl BlocksState {
    /// Initialize the `BlocksState` structure
    pub fn new() -> Self {
        Self {
            block_statuses: PreHashMap::default(),
            incoming_index: PreHashSet::default(),
            sequence_counter: 0,
            waiting_for_slot_index: PreHashSet::default(),
            waiting_for_dependencies_index: PreHashSet::default(),
            discarded_index: PreHashSet::default(),
            active_index: PreHashSet::default(),
        }
    }

    /// Get a reference on a `BlockStatus` from a `BlockId`
    pub fn get(&self, block_id: &BlockId) -> Option<&BlockStatus> {
        self.block_statuses.get(block_id)
    }

    /// Get a mutable reference on a `BlockStatus` from a `BlockId`
    pub fn get_mut(&mut self, block_id: &BlockId) -> Option<&mut BlockStatus> {
        self.block_statuses.get_mut(block_id)
    }

    /// Get the sequence counter
    pub fn sequence_counter(&self) -> u64 {
        self.sequence_counter
    }

    /// Get a reference on the list of all blocks stored with the status `Incoming`
    pub fn incoming_blocks(&self) -> &PreHashSet<BlockId> {
        &self.incoming_index
    }

    /// Get a reference on the list of all blocks stored with the status `WaitingForSlot`
    pub fn waiting_for_slot_blocks(&self) -> &PreHashSet<BlockId> {
        &self.waiting_for_slot_index
    }

    /// Get a reference on the list of all blocks stored with the status `WaitingForDependencies`
    pub fn waiting_for_dependencies_blocks(&self) -> &PreHashSet<BlockId> {
        &self.waiting_for_dependencies_index
    }

    /// Get a reference on the list of all blocks stored with the status `Discarded`
    pub fn discarded_blocks(&self) -> &PreHashSet<BlockId> {
        &self.discarded_index
    }

    /// Get a reference on the list of all blocks stored with the status `Active`
    pub fn active_blocks(&self) -> &PreHashSet<BlockId> {
        &self.active_index
    }

    // Internal function to update the indexes
    fn update_indexes(
        &mut self,
        block_id: &BlockId,
        old_block_status: Option<&BlockStatusId>,
        new_block_status: Option<&BlockStatusId>,
    ) {
        if let Some(old_block_status) = old_block_status {
            match old_block_status {
                BlockStatusId::Incoming => {
                    self.incoming_index.remove(block_id);
                }
                BlockStatusId::WaitingForSlot => {
                    self.waiting_for_slot_index.remove(block_id);
                }
                BlockStatusId::WaitingForDependencies => {
                    self.waiting_for_dependencies_index.remove(block_id);
                }
                BlockStatusId::Discarded => {
                    self.discarded_index.remove(block_id);
                }
                BlockStatusId::Active => {
                    self.active_index.remove(block_id);
                }
            }
        }
        if let Some(new_block_status) = new_block_status {
            match new_block_status {
                BlockStatusId::Incoming => {
                    self.incoming_index.insert(*block_id);
                }
                BlockStatusId::WaitingForSlot => {
                    self.waiting_for_slot_index.insert(*block_id);
                }
                BlockStatusId::WaitingForDependencies => {
                    self.waiting_for_dependencies_index.insert(*block_id);
                }
                BlockStatusId::Discarded => {
                    self.discarded_index.insert(*block_id);
                }
                BlockStatusId::Active => {
                    self.active_index.insert(*block_id);
                }
            }
        }
    }

    /// Promote a block id and all of its dependencies to be the first in the sequence
    fn promote_dep_tree(&mut self, hash: BlockId) {
        let mut to_explore = vec![hash];
        let mut to_promote: PreHashMap<BlockId, (Slot, u64)> = PreHashMap::default();
        while let Some(h) = to_explore.pop() {
            if to_promote.contains_key(&h) {
                continue;
            }
            if let Some(BlockStatus::WaitingForDependencies {
                header_or_block,
                unsatisfied_dependencies,
                sequence_number,
                ..
            }) = self.block_statuses.get(&h)
            {
                // promote current block
                to_promote.insert(h, (header_or_block.get_slot(), *sequence_number));
                // register dependencies for exploration
                to_explore.extend(unsatisfied_dependencies);
            }
        }

        let mut to_promote: Vec<(Slot, u64, BlockId)> = to_promote
            .into_iter()
            .map(|(h, (slot, seq))| (slot, seq, h))
            .collect();
        to_promote.sort_unstable(); // last ones should have the highest seq number
        for (_slot, _seq, h) in to_promote.into_iter() {
            if let Some(BlockStatus::WaitingForDependencies {
                sequence_number, ..
            }) = self.block_statuses.get_mut(&h)
            {
                self.sequence_counter += 1;
                *sequence_number = self.sequence_counter;
            }
        }
    }

    /// Get an iterator over all the blocks stored in the `BlocksState`
    pub fn iter(&self) -> impl Iterator<Item = (&BlockId, &BlockStatus)> + '_ {
        self.block_statuses.iter()
    }

    /// Get the number of blocks stored in the `BlocksState`
    pub fn len(&self) -> usize {
        self.block_statuses.len()
    }

    /// Change the state of a block
    /// Steps are:
    /// 1. Remove the block from state
    /// 2. Call the callback function with the removed state (or None if block wasn't in the `BlocksState`) as parameter and retrieve the new state (or None if we don't want to add the block again in the `BlocksState`)
    /// 3. Verify that the transition is valid
    /// 4. Insert the new state in the `BlocksState`
    pub fn transition_map<
        F: FnOnce(Option<BlockStatus>, &mut PreHashMap<BlockId, BlockStatus>) -> Option<BlockStatus>,
    >(
        &mut self,
        block_id: &BlockId,
        callback: F,
    ) {
        match self.block_statuses.remove(block_id) {
            Some(block) => {
                let old_state_id = BlockStatusId::from(&block);
                self.update_indexes(block_id, Some(&old_state_id), None);
                let Some(mut new_state) = callback(Some(block), &mut self.block_statuses) else {
                    return;
                };
                let new_state_id = BlockStatusId::from(&new_state);
                match (&old_state_id, &new_state_id) {
                    // From incoming status
                    (BlockStatusId::Incoming, BlockStatusId::WaitingForDependencies) => {
                        if let BlockStatus::WaitingForDependencies {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                        self.promote_dep_tree(*block_id);
                    }
                    (BlockStatusId::Incoming, BlockStatusId::Discarded) => {
                        if let BlockStatus::Discarded {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (BlockStatusId::Incoming, _) => {
                        self.block_statuses.insert(*block_id, new_state);
                    }

                    // From waiting for slot status
                    (BlockStatusId::WaitingForSlot, BlockStatusId::Incoming) => {
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (BlockStatusId::WaitingForSlot, BlockStatusId::Discarded) => {
                        if let BlockStatus::Discarded {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (BlockStatusId::WaitingForSlot, BlockStatusId::WaitingForSlot) => {
                        self.block_statuses.insert(*block_id, new_state);
                    }

                    // From waiting for dependencies status
                    (BlockStatusId::WaitingForDependencies, BlockStatusId::Incoming) => {
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (BlockStatusId::WaitingForDependencies, BlockStatusId::Discarded) => {
                        if let BlockStatus::Discarded {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (
                        BlockStatusId::WaitingForDependencies,
                        BlockStatusId::WaitingForDependencies,
                    ) => {
                        self.block_statuses.insert(*block_id, new_state);
                        self.promote_dep_tree(*block_id);
                    }

                    // From active status
                    (BlockStatusId::Active, BlockStatusId::Discarded) => {
                        if let BlockStatus::Discarded {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    (BlockStatusId::Active, BlockStatusId::Active) => {
                        self.block_statuses.insert(*block_id, new_state);
                    }

                    // From discarded status
                    (BlockStatusId::Discarded, BlockStatusId::Discarded) => {
                        if let BlockStatus::Discarded {
                            sequence_number, ..
                        } = &mut new_state
                        {
                            self.sequence_counter += 1;
                            *sequence_number = self.sequence_counter;
                        } else {
                            panic!("Unexpected block status for {}", block_id);
                        }
                        self.block_statuses.insert(*block_id, new_state);
                    }
                    // All other possibilities are invalid
                    (old_state_id, new_state_id) => {
                        panic!(
                            "Invalid transition from {:?} to {:?} for block {}",
                            old_state_id, new_state_id, block_id
                        );
                    }
                }
                self.update_indexes(block_id, None, Some(&new_state_id));
            }
            None => {
                let new_state = callback(None, &mut self.block_statuses);
                if let Some(new_state) = new_state {
                    let state = BlockStatusId::from(&new_state);
                    if state != BlockStatusId::Incoming && state != BlockStatusId::Active {
                        panic!(
                            "Invalid transition from None to {:?} for block {}",
                            state, block_id
                        );
                    }
                    self.block_statuses.insert(*block_id, new_state);
                    self.update_indexes(block_id, None, Some(&state));
                }
            }
        };
    }
}