1use std::cmp::Ordering;
2use std::collections::hash_map::Entry;
3use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
4use std::iter;
5use std::ops::Deref;
6use std::sync::Arc;
7
8use machine::{machine, transitions};
9use num::{rational::Ratio, Zero};
10use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
11use parking_lot::RwLock;
12use thiserror::Error;
13use tracing::{debug, warn};
14
15use massa_db_exports::{
16 DBBatch, ShareableMassaDBController, MIP_STORE_PREFIX, MIP_STORE_STATS_PREFIX, STATE_CF,
17 VERSIONING_CF,
18};
19use massa_models::config::MIP_STORE_STATS_BLOCK_CONSIDERED;
20#[allow(unused_imports)]
21use massa_models::config::VERSIONING_ACTIVATION_DELAY_MIN;
22use massa_models::config::VERSIONING_THRESHOLD_TRANSITION_ACCEPTED;
23use massa_models::error::ModelsError;
24use massa_models::slot::Slot;
25use massa_models::timeslots::get_block_slot_timestamp;
26use massa_serialization::{DeserializeError, Deserializer, SerializeError, Serializer};
27use massa_time::MassaTime;
28use variant_count::VariantCount;
29
30use crate::versioning_ser_der::{
31 MipInfoDeserializer, MipInfoSerializer, MipStateDeserializer, MipStateSerializer,
32 MipStoreStatsDeserializer, MipStoreStatsSerializer,
33};
34
35#[allow(missing_docs)]
37#[derive(
38 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, FromPrimitive, IntoPrimitive, VariantCount,
39)]
40#[repr(u32)]
41pub enum MipComponent {
42 Address,
44 KeyPair,
45 Block,
46 VM,
47 FinalStateHashKind,
48 Execution,
49 FinalState,
50 #[doc(hidden)]
51 #[num_enum(default)]
52 __Nonexhaustive,
53}
54
55#[derive(Clone, Debug)]
57pub struct MipInfo {
58 pub name: String,
60 pub version: u32,
62 pub components: BTreeMap<MipComponent, u32>,
64 pub start: MassaTime,
66 pub timeout: MassaTime,
68 pub activation_delay: MassaTime,
70}
71
72impl Ord for MipInfo {
75 fn cmp(&self, other: &Self) -> Ordering {
76 (
77 self.start,
78 self.timeout,
79 self.activation_delay,
80 &self.name,
81 &self.version,
82 &self.components,
83 )
84 .cmp(&(
85 other.start,
86 other.timeout,
87 other.activation_delay,
88 &other.name,
89 &other.version,
90 &other.components,
91 ))
92 }
93}
94
95impl PartialOrd for MipInfo {
96 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97 Some(self.cmp(other))
98 }
99}
100
101impl PartialEq for MipInfo {
102 fn eq(&self, other: &Self) -> bool {
103 self.name == other.name
104 && self.version == other.version
105 && self.components == other.components
106 && self.start == other.start
107 && self.timeout == other.timeout
108 && self.activation_delay == other.activation_delay
109 }
110}
111
112impl Eq for MipInfo {}
113
114machine!(
115 #[derive(Clone, Copy, Debug, PartialEq)]
117 pub(crate) enum ComponentState {
118 Defined,
120 Started { pub(crate) vote_ratio: Ratio<u64> },
122 LockedIn { pub(crate) at: MassaTime },
125 Active { pub(crate) at: MassaTime },
128 Failed,
130 }
131);
132
133impl Default for ComponentState {
134 fn default() -> Self {
135 Self::Defined(Defined {})
136 }
137}
138
139impl ComponentState {
140 fn is_final(&self) -> bool {
141 matches!(
142 self,
143 ComponentState::Active(..) | ComponentState::Failed(..) | ComponentState::Error
144 )
145 }
146}
147
148#[allow(missing_docs)]
149#[derive(
150 IntoPrimitive, Debug, Clone, Eq, PartialEq, TryFromPrimitive, PartialOrd, Ord, VariantCount,
151)]
152#[repr(u32)]
153pub enum ComponentStateTypeId {
154 Error = 0,
155 Defined = 1,
156 Started = 2,
157 LockedIn = 3,
158 Active = 4,
159 Failed = 5,
160}
161
162impl From<&ComponentState> for ComponentStateTypeId {
163 fn from(value: &ComponentState) -> Self {
164 match value {
165 ComponentState::Error => ComponentStateTypeId::Error,
166 ComponentState::Defined(_) => ComponentStateTypeId::Defined,
167 ComponentState::Started(_) => ComponentStateTypeId::Started,
168 ComponentState::LockedIn(_) => ComponentStateTypeId::LockedIn,
169 ComponentState::Active(_) => ComponentStateTypeId::Active,
170 ComponentState::Failed(_) => ComponentStateTypeId::Failed,
171 }
172 }
173}
174
175#[derive(Clone, Debug)]
177pub struct Advance {
178 pub start_timestamp: MassaTime,
180 pub timeout: MassaTime,
182 pub activation_delay: MassaTime,
184
185 pub threshold: Ratio<u64>,
187 pub now: MassaTime,
189}
190
191impl PartialEq for Advance {
192 fn eq(&self, other: &Self) -> bool {
193 self.start_timestamp == other.start_timestamp
194 && self.timeout == other.timeout
195 && self.threshold == other.threshold
196 && self.now == other.now
197 && self.activation_delay == other.activation_delay
198 }
199}
200
201impl Eq for Advance {}
202
203transitions!(ComponentState,
204 [
205 (Defined, Advance) => [Defined, Started, Failed],
206 (Started, Advance) => [Started, LockedIn, Failed],
207 (LockedIn, Advance) => [LockedIn, Active],
208 (Active, Advance) => Active,
209 (Failed, Advance) => Failed
210 ]
211);
212
213impl Defined {
214 pub fn on_advance(self, input: Advance) -> ComponentState {
216 match input.now {
217 n if n >= input.timeout => ComponentState::failed(),
218 n if n >= input.start_timestamp => ComponentState::started(Ratio::zero()),
219 _ => ComponentState::Defined(Defined {}),
220 }
221 }
222}
223
224impl Started {
225 pub fn on_advance(self, input: Advance) -> ComponentState {
227 if input.now >= input.timeout {
228 return ComponentState::failed();
229 }
230
231 if input.threshold >= VERSIONING_THRESHOLD_TRANSITION_ACCEPTED {
232 debug!("(VERSIONING LOG) transition accepted, locking in");
233 ComponentState::locked_in(input.now)
234 } else {
235 ComponentState::started(input.threshold)
236 }
237 }
238}
239
240impl LockedIn {
241 pub fn on_advance(self, input: Advance) -> ComponentState {
243 if input.now > self.at.saturating_add(input.activation_delay) {
244 debug!("(VERSIONING LOG) locked version has become active");
245 ComponentState::active(input.now)
246 } else {
247 ComponentState::locked_in(self.at)
248 }
249 }
250}
251
252impl Active {
253 pub fn on_advance(self, _input: Advance) -> Active {
255 Active { at: self.at }
256 }
257}
258
259impl Failed {
260 pub fn on_advance(self, _input: Advance) -> Failed {
262 Failed {}
263 }
264}
265
266#[derive(Clone, Debug)]
268pub struct AdvanceLW {
269 pub threshold: Ratio<u64>,
271 pub now: MassaTime,
273}
274
275impl From<&Advance> for AdvanceLW {
276 fn from(value: &Advance) -> Self {
277 Self {
278 threshold: value.threshold,
279 now: value.now,
280 }
281 }
282}
283
284impl Ord for AdvanceLW {
285 fn cmp(&self, other: &Self) -> Ordering {
286 (self.now, self.threshold).cmp(&(other.now, other.threshold))
287 }
288}
289
290impl PartialOrd for AdvanceLW {
291 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
292 Some(self.cmp(other))
293 }
294}
295
296impl PartialEq for AdvanceLW {
297 fn eq(&self, other: &Self) -> bool {
298 self.threshold == other.threshold && self.now == other.now
299 }
300}
301
302impl Eq for AdvanceLW {}
303
304#[derive(Error, Debug, PartialEq)]
306pub enum IsConsistentError {
307 #[error("MipState history is empty")]
309 EmptyHistory,
310 #[error("MipState is at state Error")]
311 AtError,
312 #[error("History must start at state 'Defined' and not {0:?}")]
313 InvalidHistory(ComponentStateTypeId),
314 #[error("Non consistent state: {0:?} versus rebuilt state: {1:?}")]
315 NonConsistent(ComponentState, ComponentState),
316 #[error("Invalid data in MIP info, start >= timeout")]
317 Invalid,
318}
319
320#[derive(Debug, Clone, PartialEq)]
322pub struct MipState {
323 pub(crate) state: ComponentState,
324 pub(crate) history: BTreeMap<AdvanceLW, ComponentStateTypeId>,
325}
326
327impl MipState {
328 pub fn new(defined: MassaTime) -> Self {
330 let state: ComponentState = Default::default(); let state_id = ComponentStateTypeId::from(&state);
332 let advance = AdvanceLW {
335 threshold: Default::default(),
336 now: defined,
337 };
339
340 let history = BTreeMap::from([(advance, state_id)]);
341 Self { state, history }
342 }
343
344 pub fn reset_from(&self) -> Option<Self> {
346 match self.history.first_key_value() {
347 Some((advance, state_id)) if *state_id == ComponentStateTypeId::Defined => {
348 Some(MipState::new(advance.now))
349 }
350 _ => None,
351 }
352 }
353
354 pub fn on_advance(&mut self, input: &Advance) {
357 let is_forward = self
360 .history
361 .last_key_value()
362 .map(|(adv, _)| adv.now < input.now)
363 .unwrap_or(false);
364
365 if is_forward {
366 let state = self.state.on_advance(input.clone());
368 if state != self.state {
370 if !(matches!(state, ComponentState::Started(Started { .. }))
373 && matches!(self.state, ComponentState::Started(Started { .. })))
374 {
375 self.history
376 .insert(input.into(), ComponentStateTypeId::from(&state));
377 }
378 self.state = state;
379 }
380 }
381 }
382
383 pub fn is_consistent_with(&self, mip_info: &MipInfo) -> Result<(), IsConsistentError> {
389 if matches!(&self.state, &ComponentState::Error) {
391 return Err(IsConsistentError::AtError);
392 }
393
394 if mip_info.start >= mip_info.timeout {
395 return Err(IsConsistentError::Invalid);
396 }
397
398 if self.history.is_empty() {
399 return Err(IsConsistentError::EmptyHistory);
400 }
401
402 let (initial_ts, initial_state_id) = self.history.first_key_value().unwrap();
404 if *initial_state_id != ComponentStateTypeId::Defined {
405 return Err(IsConsistentError::InvalidHistory(initial_state_id.clone()));
407 }
408
409 if mip_info.start < initial_ts.now || mip_info.timeout < initial_ts.now {
410 return Err(IsConsistentError::InvalidHistory(initial_state_id.clone()));
412 }
413
414 let mut vsh = MipState::new(initial_ts.now);
417 let mut advance_msg = Advance {
418 start_timestamp: mip_info.start,
419 timeout: mip_info.timeout,
420 threshold: Ratio::zero(),
421 now: initial_ts.now,
422 activation_delay: mip_info.activation_delay,
423 };
424
425 for (adv, _state) in self.history.iter().skip(1) {
426 advance_msg.now = adv.now;
427 advance_msg.threshold = adv.threshold;
428 vsh.on_advance(&advance_msg);
429 }
430
431 if let (
434 ComponentState::Started(Started {
435 vote_ratio: threshold,
436 }),
437 ComponentState::Started(Started {
438 vote_ratio: threshold_2,
439 }),
440 ) = (vsh.state, self.state)
441 {
442 if threshold_2 != threshold {
443 advance_msg.threshold = threshold_2;
444 advance_msg.now = advance_msg.now.saturating_add(MassaTime::from_millis(1));
446 vsh.on_advance(&advance_msg);
447 }
448 }
449
450 if vsh == *self {
451 Ok(())
452 } else {
453 Err(IsConsistentError::NonConsistent(self.state, vsh.state))
454 }
455 }
456
457 pub fn state_at(
460 &self,
461 ts: MassaTime,
462 start: MassaTime,
463 timeout: MassaTime,
464 activation_delay: MassaTime,
465 ) -> Result<ComponentStateTypeId, StateAtError> {
466 if self.history.is_empty() {
467 return Err(StateAtError::EmptyHistory);
468 }
469
470 let first = self.history.first_key_value().unwrap(); if ts < first.0.now {
473 return Err(StateAtError::BeforeInitialState(first.1.clone(), ts));
475 }
476
477 let mut lower_bound = None;
479 let mut higher_bound = None;
480 let mut is_after_last = false;
481
482 let last = self.history.last_key_value().unwrap(); if ts > last.0.now {
485 lower_bound = Some(last);
486 is_after_last = true;
487 }
488
489 if !is_after_last {
490 for (adv, state_id) in self.history.iter() {
492 if adv.now <= ts {
493 lower_bound = Some((adv, state_id));
494 }
495 if adv.now >= ts && higher_bound.is_none() {
496 higher_bound = Some((adv, state_id));
497 break;
498 }
499 }
500 }
501
502 match (lower_bound, higher_bound) {
503 (Some((_adv_1, st_id_1)), Some((_adv_2, _st_id_2))) => {
504 Ok(st_id_1.clone())
506 }
507 (Some((adv, st_id)), None) => {
508 let threshold_for_transition = VERSIONING_THRESHOLD_TRANSITION_ACCEPTED;
510 if *st_id == ComponentStateTypeId::Started
512 && adv.threshold < threshold_for_transition
513 && ts < timeout
514 {
516 Err(StateAtError::Unpredictable)
517 } else {
518 let msg = Advance {
519 start_timestamp: start,
520 timeout,
521 threshold: adv.threshold,
522 now: ts,
523 activation_delay,
525 };
526 let state = self.state.on_advance(msg);
528 Ok(ComponentStateTypeId::from(&state))
529 }
530 }
531 _ => {
532 Err(StateAtError::EmptyHistory)
535 }
536 }
537 }
538
539 pub fn activation_at(&self, mip_info: &MipInfo) -> Option<MassaTime> {
541 match self.state {
542 ComponentState::LockedIn(LockedIn { at }) => {
543 Some(at.saturating_add(mip_info.activation_delay))
544 }
545 _ => None,
546 }
547 }
548
549 pub fn is_final(&self) -> bool {
551 self.state.is_final()
552 }
553}
554
555#[allow(missing_docs)]
557#[derive(Error, Debug, PartialEq)]
558pub enum StateAtError {
559 #[error("Initial state ({0:?}) only defined after timestamp: {1}")]
560 BeforeInitialState(ComponentStateTypeId, MassaTime),
561 #[error("Empty history, should never happen")]
562 EmptyHistory,
563 #[error("Cannot predict value: threshold not reached yet")]
564 Unpredictable,
565}
566
567#[derive(Debug, Clone)]
571pub struct MipStore(pub Arc<RwLock<MipStoreRaw>>);
572
573impl MipStore {
574 pub fn get_network_version_current(&self) -> u32 {
580 let lock = self.0.read();
581 let store = lock.deref();
582 store
584 .store
585 .iter()
586 .rev()
587 .find_map(|(k, v)| (matches!(v.state, ComponentState::Active(_))).then_some(k.version))
588 .unwrap_or(0)
589 }
590
591 pub fn get_network_version_active_at(&self, ts: MassaTime) -> u32 {
608 let lock = self.0.read();
609 let store = lock.deref();
610 store
611 .store
612 .iter()
613 .rev()
614 .find_map(|(k, v)| match v.state {
615 ComponentState::Active(Active { at }) if at <= ts => Some(k.version),
616 ComponentState::LockedIn(LockedIn { at })
617 if ts > at.saturating_add(k.activation_delay) =>
618 {
619 Some(k.version)
620 }
621 _ => None,
622 })
623 .unwrap_or(0)
624 }
625
626 pub fn get_network_version_to_announce_at(&self, ts: MassaTime) -> Option<u32> {
633 let lock = self.0.read();
634 let store = lock.deref();
635 store.store.iter().rev().find_map(|(k, v)| match v.state {
636 ComponentState::Started(_) => Some(k.version),
637 ComponentState::LockedIn(LockedIn { at })
638 if ts <= at.saturating_add(k.activation_delay) =>
639 {
640 Some(k.version)
641 }
642 _ => None,
643 })
644 }
645
646 pub fn get_network_version_to_announce(&self) -> Option<u32> {
649 let lock = self.0.read();
650 let store = lock.deref();
651 store.store.iter().rev().find_map(|(k, v)| {
655 matches!(
656 &v.state,
657 &ComponentState::Started(_) | &ComponentState::LockedIn(_)
658 )
659 .then_some(k.version)
660 })
661 }
662
663 pub fn update_network_version_stats(
664 &mut self,
665 slot_timestamp: MassaTime,
666 network_versions: Option<(u32, Option<u32>)>,
667 ) {
668 let mut lock = self.0.write();
669 lock.update_network_version_stats(slot_timestamp, network_versions);
670 }
671
672 #[allow(clippy::result_large_err)]
673 pub fn update_with(
674 &mut self,
675 mip_store: &MipStore,
676 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), UpdateWithError> {
677 let mut lock = self.0.write();
678 let lock_other = mip_store.0.read();
679 lock.update_with(lock_other.deref())
680 }
681
682 pub fn get_latest_component_version_at(&self, component: &MipComponent, ts: MassaTime) -> u32 {
694 let guard = self.0.read();
695 guard.get_latest_component_version_at(component, ts)
696 }
697
698 pub(crate) fn get_all_active_component_versions(&self, component: &MipComponent) -> Vec<u32> {
700 let guard = self.0.read();
701 guard.get_all_active_component_versions(component)
702 }
703
704 pub(crate) fn get_all_component_versions(
706 &self,
707 component: &MipComponent,
708 ) -> BTreeMap<u32, ComponentStateTypeId> {
709 let guard = self.0.read();
710 guard.get_all_component_versions(component)
711 }
712
713 pub fn get_mip_status(&self) -> BTreeMap<MipInfo, ComponentStateTypeId> {
717 let guard = self.0.read();
718 guard
719 .store
720 .iter()
721 .map(|(mip_info, mip_state)| {
722 (
723 mip_info.clone(),
724 ComponentStateTypeId::from(&mip_state.state),
725 )
726 })
727 .collect()
728 }
729
730 pub fn is_consistent_with_shutdown_period(
732 &self,
733 shutdown_start: Slot,
734 shutdown_end: Slot,
735 thread_count: u8,
736 t0: MassaTime,
737 genesis_timestamp: MassaTime,
738 ) -> Result<(), IsConsistentWithShutdownPeriodError> {
739 let guard = self.0.read();
740 guard.is_consistent_with_shutdown_period(
741 shutdown_start,
742 shutdown_end,
743 thread_count,
744 t0,
745 genesis_timestamp,
746 )
747 }
748
749 #[allow(dead_code)]
750 pub fn update_for_network_shutdown(
751 &mut self,
752 shutdown_start: Slot,
753 shutdown_end: Slot,
754 thread_count: u8,
755 t0: MassaTime,
756 genesis_timestamp: MassaTime,
757 ) -> Result<(), ModelsError> {
758 let mut guard = self.0.write();
759 guard.update_for_network_shutdown(
760 shutdown_start,
761 shutdown_end,
762 thread_count,
763 t0,
764 genesis_timestamp,
765 )
766 }
767
768 pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool {
769 let guard = self.0.read();
770 guard.is_key_value_valid(serialized_key, serialized_value)
771 }
772
773 pub fn update_batches(
776 &self,
777 db_batch: &mut DBBatch,
778 db_versioning_batch: &mut DBBatch,
779 between: Option<(&MassaTime, &MassaTime)>,
780 ) -> Result<(), SerializeError> {
781 let guard = self.0.read();
782 guard.update_batches(db_batch, db_versioning_batch, between)
783 }
784
785 pub fn extend_from_db(
786 &mut self,
787 db: ShareableMassaDBController,
788 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), ExtendFromDbError> {
789 let mut guard = self.0.write();
790 guard.extend_from_db(db)
791 }
792
793 pub fn started_mips_missing_from_db(
803 &self,
804 db: &ShareableMassaDBController,
805 at: MassaTime,
806 ) -> Vec<MipInfo> {
807 let guard = self.0.read();
808 guard.started_mips_missing_from_db(db, at)
809 }
810
811 pub fn reset_db(&self, db: ShareableMassaDBController) {
812 {
813 let mut guard = db.write();
814 guard.delete_prefix(MIP_STORE_PREFIX, STATE_CF, None);
815 guard.delete_prefix(MIP_STORE_PREFIX, VERSIONING_CF, None);
816 guard.delete_prefix(MIP_STORE_STATS_PREFIX, VERSIONING_CF, None);
817 }
818 }
819
820 pub fn try_from_db(
822 db: ShareableMassaDBController,
823 cfg: MipStatsConfig,
824 ) -> Result<Self, ExtendFromDbError> {
825 MipStoreRaw::try_from_db(db, cfg).map(|store_raw| Self(Arc::new(RwLock::new(store_raw))))
826 }
827
828 }
834
835impl<const N: usize> TryFrom<([(MipInfo, MipState); N], MipStatsConfig)> for MipStore {
836 type Error = UpdateWithError;
837
838 fn try_from(
839 (value, cfg): ([(MipInfo, MipState); N], MipStatsConfig),
840 ) -> Result<Self, Self::Error> {
841 MipStoreRaw::try_from((value, cfg)).map(|store_raw| Self(Arc::new(RwLock::new(store_raw))))
842 }
843}
844
845#[derive(Debug, Clone, PartialEq)]
847pub struct MipStatsConfig {
848 pub block_count_considered: usize,
849 pub warn_announced_version_ratio: Ratio<u64>,
850}
851
852#[derive(Debug, Clone, PartialEq)]
854pub(crate) struct MipStoreStats {
855 pub(crate) config: MipStatsConfig,
857 pub(crate) latest_announcements: VecDeque<u32>,
860 pub(crate) network_version_counters: HashMap<u32, u64>,
864}
865
866impl MipStoreStats {
867 pub(crate) fn new(config: MipStatsConfig) -> Self {
868 Self {
869 config: config.clone(),
870 latest_announcements: VecDeque::with_capacity(config.block_count_considered),
871 network_version_counters: HashMap::with_capacity(config.block_count_considered),
872 }
873 }
874
875 fn reset(&mut self) {
877 self.latest_announcements.clear();
878 self.network_version_counters.clear();
879 }
880}
881
882#[derive(Error, Debug, PartialEq)]
884pub enum UpdateWithError {
885 #[error("MipInfo {0:#?} is not consistent with state: {1:#?}, error: {2}")]
887 NonConsistent(MipInfo, MipState, IsConsistentError),
888 #[error("For MipInfo {0:?}, trying to downgrade from state {1:?} to {2:?}")]
890 Downgrade(MipInfo, ComponentState, ComponentState),
891 #[error("MipInfo {0:?} has overlapping data of MipInfo {1:?}")]
893 Overlapping(MipInfo, MipInfo),
894 #[error("MipInfo {0:?} has an invalid activation delay value: {1}, min allowed: {2}")]
895 InvalidActivationDelay(MipInfo, MassaTime, MassaTime),
896}
897
898#[derive(Error, Debug)]
900pub enum ExtendFromDbError {
901 #[error("Unable to get an handle over db column: {0}")]
902 UnknownDbColumn(String),
903 #[error("{0}")]
904 Update(#[from] UpdateWithError),
905 #[error("{0}")]
906 Deserialize(String),
907}
908
909#[derive(Error, Debug)]
911pub enum IsConsistentWithShutdownPeriodError {
912 #[error("{0}")]
913 Update(#[from] ModelsError),
914 #[error("MipInfo: {0:?} (state: {1:?}) is not consistent with shutdown: {2} {3}")]
915 NonConsistent(MipInfo, ComponentState, MassaTime, MassaTime),
916}
917
918#[derive(Error, Debug)]
919pub enum IsKVValidError {
920 #[error("{0}")]
921 Deserialize(String),
922 #[error("Invalid prefix for key")]
923 InvalidPrefix,
924}
925
926#[derive(Debug, Clone, PartialEq)]
928pub struct MipStoreRaw {
929 pub(crate) store: BTreeMap<MipInfo, MipState>,
930 pub(crate) stats: MipStoreStats,
931}
932
933impl MipStoreRaw {
934 #[allow(clippy::result_large_err)]
937 pub fn update_with(
938 &mut self,
939 store_raw: &MipStoreRaw,
940 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), UpdateWithError> {
941 let mut component_versions: HashMap<MipComponent, u32> = self
950 .store
951 .iter()
952 .flat_map(|c| {
953 c.0.components
954 .iter()
955 .map(|(mip_component, component_version)| {
956 (mip_component.clone(), *component_version)
957 })
958 })
959 .collect();
960 let mut names: HashSet<String> = self.store.keys().map(|mi| mi.name.clone()).collect();
961 let mut to_update: BTreeMap<MipInfo, MipState> = Default::default();
962 let mut to_add: BTreeMap<MipInfo, MipState> = Default::default();
963 let mut has_error: Option<UpdateWithError> = None;
964
965 for (m_info, m_state) in store_raw.store.iter() {
966 if let Err(e) = m_state.is_consistent_with(m_info) {
967 has_error = Some(UpdateWithError::NonConsistent(
969 m_info.clone(),
970 m_state.clone(),
971 e,
972 ));
973 break;
974 }
975
976 if let Some(m_state_orig) = self.store.get(m_info) {
977 let m_state_id: u32 = ComponentStateTypeId::from(&m_state.state).into();
980 let m_state_orig_id: u32 = ComponentStateTypeId::from(&m_state_orig.state).into();
981
982 if matches!(
984 m_state_orig.state,
985 ComponentState::Defined(_)
986 | ComponentState::Started(_)
987 | ComponentState::LockedIn(_)
988 ) {
989 if m_state_id >= m_state_orig_id {
992 to_update.insert(m_info.clone(), m_state.clone());
993 } else {
994 has_error = Some(UpdateWithError::Downgrade(
996 m_info.clone(),
997 m_state_orig.state,
998 m_state.state,
999 ));
1000 break;
1001 }
1002 }
1003 } else {
1004 let last_m_info_ = to_add
1008 .last_key_value()
1009 .map(|(mi, _)| mi)
1010 .or(self.store.last_key_value().map(|(mi, _)| mi));
1011
1012 if let Some(last_m_info) = last_m_info_ {
1013 let mut component_version_compatible = true;
1015 for (component, component_version) in m_info.components.iter() {
1016 if component_version <= component_versions.get(component).unwrap_or(&0) {
1017 component_version_compatible = false;
1018 break;
1019 }
1020 }
1021
1022 #[cfg(not(any(test, feature = "test-exports")))]
1023 if m_info.activation_delay < VERSIONING_ACTIVATION_DELAY_MIN {
1024 has_error = Some(UpdateWithError::InvalidActivationDelay(
1025 m_info.clone(),
1026 m_info.activation_delay,
1027 VERSIONING_ACTIVATION_DELAY_MIN,
1028 ));
1029 break;
1030 }
1031
1032 if m_info.start > last_m_info.timeout
1033 && m_info.timeout > m_info.start
1034 && m_info.version > last_m_info.version
1035 && !names.contains(&m_info.name)
1036 && component_version_compatible
1037 {
1038 to_add.insert(m_info.clone(), m_state.clone());
1040 names.insert(m_info.name.clone());
1041 for (component, component_version) in m_info.components.iter() {
1042 component_versions.insert(component.clone(), *component_version);
1043 }
1044 } else {
1045 has_error = Some(UpdateWithError::Overlapping(
1048 m_info.clone(),
1049 last_m_info.clone(),
1050 ));
1051 break;
1052 }
1053 } else {
1054 to_add.insert(m_info.clone(), m_state.clone());
1056 names.insert(m_info.name.clone());
1057 }
1058 }
1059 }
1060
1061 match has_error {
1062 None => {
1063 let updated: Vec<MipInfo> = to_update.keys().cloned().collect();
1064
1065 self.store.append(&mut to_update);
1069 Ok((updated, to_add))
1070 }
1071 Some(e) => Err(e),
1072 }
1073 }
1074
1075 pub fn update_network_version_stats(
1076 &mut self,
1077 slot_timestamp: MassaTime,
1078 network_versions: Option<(u32, Option<u32>)>,
1079 ) {
1080 if let Some((_current_network_version, announced_network_version_)) = network_versions {
1081 let announced_network_version = announced_network_version_.unwrap_or(0);
1082
1083 let removed_version_ = match self.stats.latest_announcements.len() {
1084 n if n >= self.stats.config.block_count_considered => {
1085 self.stats.latest_announcements.pop_front()
1086 }
1087 _ => None,
1088 };
1089 self.stats
1090 .latest_announcements
1091 .push_back(announced_network_version);
1092
1093 let mut network_version_count = *self
1095 .stats
1096 .network_version_counters
1097 .entry(announced_network_version)
1098 .and_modify(|v| *v = v.saturating_add(1))
1099 .or_insert(1);
1100
1101 if let Some(removed_version) = removed_version_ {
1105 if let Entry::Occupied(mut e) =
1106 self.stats.network_version_counters.entry(removed_version)
1107 {
1108 let entry_value = e.get_mut();
1109 *entry_value = entry_value.saturating_sub(1);
1110 network_version_count = *entry_value;
1111 if *entry_value == 0 {
1112 self.stats.network_version_counters.remove(&removed_version);
1113 }
1114 }
1115 }
1116
1117 if announced_network_version != 0 {
1118 let vote_ratio = Ratio::new(
1119 network_version_count,
1120 self.stats.config.block_count_considered as u64,
1121 );
1122
1123 if vote_ratio > self.stats.config.warn_announced_version_ratio {
1124 let last_key_value = self.store.last_key_value();
1125 if let Some((mi, _ms)) = last_key_value {
1126 if announced_network_version > mi.version {
1127 warn!("{} our of {} last blocks advertised that they are willing to transition to version {}. You should update your node if you wish to move to that version.",
1132 network_version_count,
1133 self.stats.config.block_count_considered,
1134 announced_network_version
1135 );
1136 }
1137 }
1138 }
1139 }
1140 }
1141
1142 debug!(
1143 "[VERSIONING STATS] stats have {} counters and {} announcements",
1144 self.stats.network_version_counters.len(),
1145 self.stats.latest_announcements.len()
1146 );
1147
1148 self.advance_states_on_updated_stats(slot_timestamp);
1150 }
1151
1152 fn advance_states_on_updated_stats(&mut self, slot_timestamp: MassaTime) {
1154 for (mi, state) in self.store.iter_mut() {
1155 if state.is_final() {
1156 continue;
1158 }
1159
1160 let network_version_count = *self
1161 .stats
1162 .network_version_counters
1163 .get(&mi.version)
1164 .unwrap_or(&0);
1165
1166 let vote_ratio = Ratio::new(
1167 network_version_count,
1168 self.stats.config.block_count_considered as u64,
1169 );
1170
1171 debug!("[VERSIONING STATS] Vote counts / blocks considered = {} / {} (for MipInfo with network version {} - {})",
1172 network_version_count,
1173 self.stats.config.block_count_considered,
1174 mi.version,
1175 mi.name);
1176
1177 let advance_msg = Advance {
1178 start_timestamp: mi.start,
1179 timeout: mi.timeout,
1180 threshold: vote_ratio,
1181 now: slot_timestamp,
1182 activation_delay: mi.activation_delay,
1183 };
1184
1185 state.on_advance(&advance_msg.clone());
1186 }
1187 }
1188
1189 fn get_latest_component_version_at(&self, component: &MipComponent, ts: MassaTime) -> u32 {
1198 let version = self
1199 .store
1200 .iter()
1201 .rev()
1202 .filter(|(mi, ms)| {
1203 mi.components.contains_key(component)
1204 && matches!(
1205 ms.state,
1206 ComponentState::Active(_) | ComponentState::LockedIn(_)
1207 )
1208 })
1209 .find_map(|(mi, ms)| {
1210 let res = ms.state_at(ts, mi.start, mi.timeout, mi.activation_delay);
1211 match res {
1212 Ok(ComponentStateTypeId::Active) => mi.components.get(component).copied(),
1213 _ => None,
1214 }
1215 })
1216 .unwrap_or(0);
1217
1218 version
1219 }
1220
1221 fn get_all_active_component_versions(&self, component: &MipComponent) -> Vec<u32> {
1223 let versions_iter = self.store.iter().filter_map(|(mi, ms)| {
1224 if matches!(ms.state, ComponentState::Active(_)) {
1225 mi.components.get(component).copied()
1226 } else {
1227 None
1228 }
1229 });
1230 let versions: Vec<u32> = iter::once(0).chain(versions_iter).collect();
1231 versions
1232 }
1233
1234 fn get_all_component_versions(
1236 &self,
1237 component: &MipComponent,
1238 ) -> BTreeMap<u32, ComponentStateTypeId> {
1239 let versions_iter = self.store.iter().filter_map(|(mi, ms)| {
1240 mi.components
1241 .get(component)
1242 .copied()
1243 .map(|component_version| (component_version, ComponentStateTypeId::from(&ms.state)))
1244 });
1245 iter::once((0, ComponentStateTypeId::Active))
1246 .chain(versions_iter)
1247 .collect()
1248 }
1249
1250 fn is_consistent_with_shutdown_period(
1257 &self,
1258 shutdown_start: Slot,
1259 shutdown_end: Slot,
1260 thread_count: u8,
1261 t0: MassaTime,
1262 genesis_timestamp: MassaTime,
1263 ) -> Result<(), IsConsistentWithShutdownPeriodError> {
1264 let mut has_error: Result<(), IsConsistentWithShutdownPeriodError> = Ok(());
1266
1267 let shutdown_start_ts =
1268 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_start)?;
1269 let shutdown_end_ts =
1270 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_end)?;
1271 let shutdown_range = shutdown_start_ts..=shutdown_end_ts;
1272
1273 for (mip_info, mip_state) in &self.store {
1274 match mip_state.state {
1275 ComponentState::Defined(..)
1276 | ComponentState::Started(..)
1277 | ComponentState::LockedIn(..) => {
1278 if shutdown_range.contains(&mip_info.start)
1280 || shutdown_range.contains(&mip_info.timeout)
1281 {
1282 has_error = Err(IsConsistentWithShutdownPeriodError::NonConsistent(
1284 mip_info.clone(),
1285 mip_state.state,
1286 shutdown_start_ts,
1287 shutdown_end_ts,
1288 ));
1289 break;
1290 }
1291 }
1292 _ => {
1293 }
1296 }
1297 }
1298
1299 has_error
1300 }
1301
1302 fn update_for_network_shutdown(
1303 &mut self,
1304 shutdown_start: Slot,
1305 shutdown_end: Slot,
1306 thread_count: u8,
1307 t0: MassaTime,
1308 genesis_timestamp: MassaTime,
1309 ) -> Result<(), ModelsError> {
1310 let shutdown_start_ts =
1311 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_start)?;
1312 let shutdown_end_ts =
1313 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_end)?;
1314 let shutdown_range = shutdown_start_ts..=shutdown_end_ts;
1315
1316 let mut new_store: BTreeMap<MipInfo, MipState> = Default::default();
1317 let mut new_stats = self.stats.clone();
1318 new_stats.reset();
1319
1320 let next_valid_start_ = shutdown_end.get_next_slot(thread_count)?;
1321 let next_valid_start =
1322 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, next_valid_start_)?;
1323
1324 let mut offset: Option<MassaTime> = None;
1325
1326 for (mip_info, mip_state) in &self.store {
1327 match mip_state.state {
1328 ComponentState::Defined(..) => {
1329 let mut new_mip_info = mip_info.clone();
1332
1333 if shutdown_range.contains(&new_mip_info.start) {
1334 let offset_ts = match offset {
1335 Some(offset_ts) => offset_ts,
1336 None => {
1337 let offset_ts = next_valid_start.saturating_sub(mip_info.start);
1338 offset = Some(offset_ts);
1339 offset_ts
1340 }
1341 };
1342
1343 new_mip_info.start = new_mip_info.start.saturating_add(offset_ts);
1344 new_mip_info.timeout = new_mip_info
1345 .start
1346 .saturating_add(mip_info.timeout.saturating_sub(mip_info.start));
1347 }
1348 new_store.insert(new_mip_info, mip_state.clone());
1349 }
1350 ComponentState::Started(..) | ComponentState::LockedIn(..) => {
1351 let mut new_mip_info = mip_info.clone();
1354
1355 let offset_ts = match offset {
1356 Some(offset_ts) => offset_ts,
1357 None => {
1358 let offset_ts = next_valid_start.saturating_sub(mip_info.start);
1359 offset = Some(offset_ts);
1360 offset_ts
1361 }
1362 };
1363
1364 new_mip_info.start = new_mip_info.start.saturating_add(offset_ts);
1365 new_mip_info.timeout = new_mip_info
1366 .start
1367 .saturating_add(mip_info.timeout.saturating_sub(mip_info.start));
1368
1369 let new_mip_state = MipState::reset_from(mip_state)
1371 .ok_or(ModelsError::from("Unable to reset state"))?;
1372 new_store.insert(new_mip_info, new_mip_state.clone());
1374 }
1375 _ => {
1376 new_store.insert(mip_info.clone(), mip_state.clone());
1378 }
1379 }
1380 }
1381
1382 self.store = new_store;
1383 self.stats = new_stats;
1384 Ok(())
1385 }
1386
1387 pub fn get_network_versions_stats(&self) -> HashMap<u32, u64> {
1390 self.stats.network_version_counters.clone()
1391 }
1392
1393 pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool {
1395 self._is_key_value_valid(serialized_key, serialized_value)
1396 .is_ok()
1397 }
1398
1399 pub fn _is_key_value_valid(
1400 &self,
1401 serialized_key: &[u8],
1402 serialized_value: &[u8],
1403 ) -> Result<(), IsKVValidError> {
1404 let mip_info_deser = MipInfoDeserializer::new();
1405 let mip_state_deser = MipStateDeserializer::new();
1406 let mip_store_stats_deser = MipStoreStatsDeserializer::new(
1407 MIP_STORE_STATS_BLOCK_CONSIDERED,
1408 self.stats.config.warn_announced_version_ratio,
1409 );
1410
1411 if serialized_key.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1412 let (rem, _mip_info) = mip_info_deser
1413 .deserialize::<DeserializeError>(&serialized_key[MIP_STORE_PREFIX.len()..])
1414 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1415
1416 if !rem.is_empty() {
1417 return Err(IsKVValidError::Deserialize(
1418 "Rem not empty after deserialization".to_string(),
1419 ));
1420 }
1421
1422 let (rem2, _mip_state) = mip_state_deser
1423 .deserialize::<DeserializeError>(serialized_value)
1424 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1425
1426 if !rem2.is_empty() {
1427 return Err(IsKVValidError::Deserialize(
1428 "Rem not empty after deserialization".to_string(),
1429 ));
1430 }
1431 } else if serialized_key.starts_with(MIP_STORE_STATS_PREFIX.as_bytes()) {
1432 let (rem, _mip_store_stats) = mip_store_stats_deser
1433 .deserialize::<DeserializeError>(serialized_value)
1434 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1435
1436 if !rem.is_empty() {
1437 return Err(IsKVValidError::Deserialize(
1438 "Rem not empty after deserialization".to_string(),
1439 ));
1440 }
1441 } else {
1442 return Err(IsKVValidError::InvalidPrefix);
1443 }
1444
1445 Ok(())
1446 }
1447
1448 pub fn update_batches(
1452 &self,
1453 batch: &mut DBBatch,
1454 versioning_batch: &mut DBBatch,
1455 between: Option<(&MassaTime, &MassaTime)>,
1456 ) -> Result<(), SerializeError> {
1457 let mip_info_ser = MipInfoSerializer::new();
1458 let mip_state_ser = MipStateSerializer::new();
1459
1460 let bounds = match between {
1461 Some(between) => (*between.0)..(*between.1),
1462 None => MassaTime::from_millis(0)..MassaTime::max(),
1463 };
1464 let mut key = Vec::new();
1465 let mut value = Vec::new();
1466
1467 for (mip_info, mip_state) in self.store.iter() {
1468 if let Some((advance, state_id)) = mip_state.history.last_key_value() {
1469 if bounds.contains(&advance.now) {
1470 key.extend(MIP_STORE_PREFIX.as_bytes().to_vec());
1471 mip_info_ser.serialize(mip_info, &mut key)?;
1472 mip_state_ser.serialize(mip_state, &mut value)?;
1473 match state_id {
1474 ComponentStateTypeId::Active => {
1475 batch.insert(key.clone(), Some(value.clone()));
1476 versioning_batch.insert(key.clone(), None);
1478 }
1479 _ => {
1480 versioning_batch.insert(key.clone(), Some(value.clone()));
1481 }
1482 }
1483 key.clear();
1484 value.clear();
1485 }
1486 }
1487 }
1488
1489 value.clear();
1490 let mip_stats_ser = MipStoreStatsSerializer::new();
1491 mip_stats_ser.serialize(&self.stats, &mut value)?;
1492 versioning_batch.insert(
1493 MIP_STORE_STATS_PREFIX.as_bytes().to_vec(),
1494 Some(value.clone()),
1495 );
1496
1497 Ok(())
1498 }
1499
1500 fn extend_from_db(
1502 &mut self,
1503 db: ShareableMassaDBController,
1504 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), ExtendFromDbError> {
1505 let mip_info_deser = MipInfoDeserializer::new();
1506 let mip_state_deser = MipStateDeserializer::new();
1507 let mip_store_stats_deser = MipStoreStatsDeserializer::new(
1508 MIP_STORE_STATS_BLOCK_CONSIDERED,
1509 self.stats.config.warn_announced_version_ratio,
1510 );
1511
1512 let db = db.read();
1513
1514 let mut update_data: BTreeMap<MipInfo, MipState> = Default::default();
1516 for (ser_mip_info, ser_mip_state) in
1517 db.prefix_iterator_cf(STATE_CF, MIP_STORE_PREFIX.as_bytes())
1518 {
1519 if !ser_mip_info.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1520 break;
1521 }
1522
1523 let (_, mip_info) = mip_info_deser
1525 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1526 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1527
1528 let (_, mip_state) = mip_state_deser
1529 .deserialize::<DeserializeError>(&ser_mip_state)
1530 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1531
1532 update_data.insert(mip_info, mip_state);
1533 }
1534
1535 let (mut updated, mut added) = match update_data.is_empty() {
1536 true => (vec![], BTreeMap::new()),
1537 false => {
1538 let store_raw_ = MipStoreRaw {
1539 store: update_data,
1540 stats: MipStoreStats {
1541 config: MipStatsConfig {
1542 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
1543 warn_announced_version_ratio: self
1544 .stats
1545 .config
1546 .warn_announced_version_ratio,
1547 },
1548 latest_announcements: Default::default(),
1549 network_version_counters: Default::default(),
1550 },
1551 };
1552 self.update_with(&store_raw_)?
1554 }
1555 };
1556
1557 let mut update_data: BTreeMap<MipInfo, MipState> = Default::default();
1558
1559 for (ser_mip_info, ser_mip_state) in
1561 db.prefix_iterator_cf(VERSIONING_CF, MIP_STORE_PREFIX.as_bytes())
1562 {
1563 match &ser_mip_info {
1566 key if key.starts_with(MIP_STORE_PREFIX.as_bytes()) => {
1567 let (_, mip_info) = mip_info_deser
1568 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1569 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1570
1571 let (_, mip_state) = mip_state_deser
1572 .deserialize::<DeserializeError>(&ser_mip_state)
1573 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1574
1575 update_data.insert(mip_info, mip_state);
1576 }
1577 key if key.starts_with(MIP_STORE_STATS_PREFIX.as_bytes()) => {
1578 let (_, mip_store_stats) = mip_store_stats_deser
1579 .deserialize::<DeserializeError>(&ser_mip_state)
1580 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1581
1582 self.stats = mip_store_stats;
1583 }
1584 _ => {
1585 break;
1586 }
1587 }
1588 }
1589
1590 if !update_data.is_empty() {
1591 let store_raw_ = MipStoreRaw {
1592 store: update_data,
1593 stats: MipStoreStats {
1594 config: MipStatsConfig {
1595 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
1596 warn_announced_version_ratio: self
1597 .stats
1598 .config
1599 .warn_announced_version_ratio,
1600 },
1601 latest_announcements: Default::default(),
1602 network_version_counters: Default::default(),
1603 },
1604 };
1605 let (updated_2, added_2) = self.update_with(&store_raw_)?;
1607 updated.extend(updated_2);
1608 added.extend(added_2);
1609 }
1610
1611 Ok((updated, added))
1612 }
1613
1614 fn started_mips_missing_from_db(
1616 &self,
1617 db: &ShareableMassaDBController,
1618 at: MassaTime,
1619 ) -> Vec<MipInfo> {
1620 let mip_info_deser = MipInfoDeserializer::new();
1621 let db = db.read();
1622
1623 let mut in_db: HashSet<String> = HashSet::new();
1626 for cf in [STATE_CF, VERSIONING_CF] {
1627 for (ser_mip_info, _) in db.prefix_iterator_cf(cf, MIP_STORE_PREFIX.as_bytes()) {
1628 if !ser_mip_info.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1629 break;
1630 }
1631 if let Ok((_, mip_info)) = mip_info_deser
1632 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1633 {
1634 in_db.insert(mip_info.name);
1635 }
1636 }
1637 }
1638
1639 self.store
1640 .keys()
1641 .filter(|mip_info| mip_info.start <= at && !in_db.contains(&mip_info.name))
1642 .cloned()
1643 .collect()
1644 }
1645
1646 fn try_from_db(
1648 db: ShareableMassaDBController,
1649 cfg: MipStatsConfig,
1650 ) -> Result<Self, ExtendFromDbError> {
1651 let mut store_raw = MipStoreRaw {
1652 store: Default::default(),
1653 stats: MipStoreStats {
1654 config: cfg,
1655 latest_announcements: Default::default(),
1656 network_version_counters: Default::default(),
1657 },
1658 };
1659
1660 let (_updated, mut added) = store_raw.extend_from_db(db)?;
1661 store_raw.store.append(&mut added);
1662 Ok(store_raw)
1663 }
1664}
1665
1666impl<const N: usize> TryFrom<([(MipInfo, MipState); N], MipStatsConfig)> for MipStoreRaw {
1667 type Error = UpdateWithError;
1668
1669 fn try_from(
1670 (value, cfg): ([(MipInfo, MipState); N], MipStatsConfig),
1671 ) -> Result<Self, Self::Error> {
1672 let mut store = Self {
1674 store: Default::default(),
1675 stats: MipStoreStats::new(cfg.clone()),
1676 };
1677
1678 let other_store = Self {
1680 store: BTreeMap::from(value),
1681 stats: MipStoreStats::new(cfg),
1682 };
1683
1684 match store.update_with(&other_store) {
1686 Ok((_updated, mut added)) => {
1687 store.store.append(&mut added);
1688 Ok(store)
1689 }
1690 Err(e) => Err(e),
1691 }
1692 }
1693}
1694
1695#[cfg(test)]
1698mod test {
1699 use super::*;
1700
1701 use assert_matches::assert_matches;
1702 use massa_db_exports::{MassaDBConfig, MassaDBController, MassaIteratorMode};
1703 use massa_db_worker::MassaDB;
1704 use more_asserts::{assert_gt, assert_le};
1705 use parking_lot::RwLock;
1706 use std::ops::{Add, Sub};
1707 use std::sync::Arc;
1708 use tempfile::tempdir;
1709
1710 use crate::test_helpers::versioning_helpers::advance_state_until;
1711
1712 use massa_models::config::{MIP_STORE_STATS_BLOCK_CONSIDERED, T0, THREAD_COUNT};
1713 use massa_models::timeslots::get_closest_slot_to_timestamp;
1714
1715 impl PartialEq<ComponentState> for MipState {
1717 fn eq(&self, other: &ComponentState) -> bool {
1718 self.state == *other
1719 }
1720 }
1721
1722 impl From<(&MipInfo, &Ratio<u64>, &MassaTime)> for Advance {
1724 fn from((mip_info, threshold, now): (&MipInfo, &Ratio<u64>, &MassaTime)) -> Self {
1725 Self {
1726 start_timestamp: mip_info.start,
1727 timeout: mip_info.timeout,
1728 threshold: *threshold,
1729 now: *now,
1730 activation_delay: mip_info.activation_delay,
1731 }
1732 }
1733 }
1734
1735 fn get_a_version_info() -> (MassaTime, MassaTime, MipInfo) {
1736 let start = MassaTime::from_utc_ymd_hms(2017, 11, 1, 7, 33, 44).unwrap();
1741 let timeout = MassaTime::from_utc_ymd_hms(2017, 11, 11, 7, 33, 44).unwrap();
1742
1743 (
1744 start,
1745 timeout,
1746 MipInfo {
1747 name: "MIP-0002".to_string(),
1748 version: 2,
1749 components: BTreeMap::from([(MipComponent::Address, 1)]),
1750 start,
1751 timeout,
1752 activation_delay: MassaTime::from_millis(20),
1753 },
1754 )
1755 }
1756
1757 #[test]
1758 fn test_state_advance_from_defined() {
1759 let (_, _, mi) = get_a_version_info();
1761 let mut state: ComponentState = Default::default();
1762 assert_eq!(state, ComponentState::defined());
1763
1764 let now = mi.start.saturating_sub(MassaTime::from_millis(1));
1765 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1766
1767 state = state.on_advance(advance_msg.clone());
1768 assert_eq!(state, ComponentState::defined());
1769
1770 let now = mi.start.saturating_add(MassaTime::from_millis(5));
1771 advance_msg.now = now;
1772 state = state.on_advance(advance_msg);
1773
1774 assert_eq!(
1776 state,
1777 ComponentState::Started(Started {
1778 vote_ratio: Ratio::zero()
1779 })
1780 );
1781 }
1782
1783 #[test]
1784 fn test_state_advance_from_started() {
1785 let (_, _, mi) = get_a_version_info();
1787 let mut state: ComponentState = ComponentState::started(Default::default());
1788
1789 let now = mi.start;
1790 let threshold_too_low =
1791 VERSIONING_THRESHOLD_TRANSITION_ACCEPTED.sub(Ratio::new_raw(10, 100));
1792 let threshold_ok = VERSIONING_THRESHOLD_TRANSITION_ACCEPTED.add(Ratio::new_raw(1, 100));
1793 assert_le!(threshold_ok, Ratio::from_integer(1));
1794 let mut advance_msg = Advance::from((&mi, &threshold_too_low, &now));
1795
1796 state = state.on_advance(advance_msg.clone());
1797 assert_eq!(state, ComponentState::started(threshold_too_low));
1798 advance_msg.threshold = threshold_ok;
1799 state = state.on_advance(advance_msg);
1800 assert_eq!(state, ComponentState::locked_in(now));
1801 }
1802
1803 #[test]
1804 fn test_state_advance_from_locked_in() {
1805 let (_, _, mi) = get_a_version_info();
1807
1808 let locked_in_at = mi.start.saturating_add(MassaTime::from_millis(1));
1809 let mut state: ComponentState = ComponentState::locked_in(locked_in_at);
1810
1811 let now = mi.start;
1812 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1813
1814 state = state.on_advance(advance_msg.clone());
1815 assert_eq!(state, ComponentState::locked_in(locked_in_at));
1816
1817 advance_msg.now = advance_msg
1818 .timeout
1819 .saturating_add(MassaTime::from_millis(1));
1820 state = state.on_advance(advance_msg);
1821 assert!(matches!(state, ComponentState::Active(_)));
1822 }
1823
1824 #[test]
1825 fn test_state_advance_from_active() {
1826 let (start, _, mi) = get_a_version_info();
1828 let mut state = ComponentState::active(start);
1829 let now = mi.start;
1830 let advance = Advance::from((&mi, &Ratio::zero(), &now));
1831
1832 state = state.on_advance(advance);
1833 assert!(matches!(state, ComponentState::Active(_)));
1834 }
1835
1836 #[test]
1837 fn test_state_advance_from_failed() {
1838 let (_, _, mi) = get_a_version_info();
1840 let mut state = ComponentState::failed();
1841 let now = mi.start;
1842 let advance = Advance::from((&mi, &Ratio::zero(), &now));
1843 state = state.on_advance(advance);
1844 assert_eq!(state, ComponentState::failed());
1845 }
1846
1847 #[test]
1848 fn test_state_advance_to_failed() {
1849 let (_, _, mi) = get_a_version_info();
1851 let now = mi.timeout.saturating_add(MassaTime::from_millis(1));
1852 let advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1853
1854 let mut state: ComponentState = Default::default(); state = state.on_advance(advance_msg.clone());
1856 assert_eq!(state, ComponentState::Failed(Failed {}));
1857
1858 let mut state = ComponentState::started(Default::default());
1859 state = state.on_advance(advance_msg.clone());
1860 assert_eq!(state, ComponentState::Failed(Failed {}));
1861 }
1862
1863 #[test]
1864 fn test_state_with_history() {
1865 let (start, _, mi) = get_a_version_info();
1868 let now_0 = start;
1869 let mut state = MipState::new(now_0);
1870
1871 assert_eq!(state, ComponentState::defined());
1872
1873 let now = mi.start.saturating_add(MassaTime::from_millis(15));
1874 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1875
1876 state.on_advance(&advance_msg);
1878 assert_eq!(state, ComponentState::started(Ratio::zero()));
1879
1880 assert_eq!(state.history.len(), 2);
1882 assert!(matches!(
1883 state.history.first_key_value(),
1884 Some((&AdvanceLW { .. }, &ComponentStateTypeId::Defined))
1885 ));
1886 assert!(matches!(
1887 state.history.last_key_value(),
1888 Some((&AdvanceLW { .. }, &ComponentStateTypeId::Started))
1889 ));
1890
1891 let state_id_ = state.state_at(
1895 mi.start.saturating_sub(MassaTime::from_millis(5)),
1896 mi.start,
1897 mi.timeout,
1898 mi.activation_delay,
1899 );
1900 assert!(matches!(
1901 state_id_,
1902 Err(StateAtError::BeforeInitialState(_, _))
1903 ));
1904 let state_id = state
1906 .state_at(mi.start, mi.start, mi.timeout, mi.activation_delay)
1907 .unwrap();
1908 assert_eq!(state_id, ComponentStateTypeId::Defined);
1909 let state_id = state
1911 .state_at(now, mi.start, mi.timeout, mi.activation_delay)
1912 .unwrap();
1913 assert_eq!(state_id, ComponentStateTypeId::Started);
1914
1915 let after_started_ts = now.saturating_add(MassaTime::from_millis(15));
1917 let state_id_ = state.state_at(after_started_ts, mi.start, mi.timeout, mi.activation_delay);
1918 assert_eq!(state_id_, Err(StateAtError::Unpredictable));
1919
1920 let after_timeout_ts = mi.timeout.saturating_add(MassaTime::from_millis(15));
1922 let state_id = state
1923 .state_at(after_timeout_ts, mi.start, mi.timeout, mi.activation_delay)
1924 .unwrap();
1925 assert_eq!(state_id, ComponentStateTypeId::Failed);
1926
1927 let threshold = VERSIONING_THRESHOLD_TRANSITION_ACCEPTED;
1929 advance_msg.threshold = threshold.add(Ratio::from_integer(1));
1930 advance_msg.now = now.saturating_add(MassaTime::from_millis(1));
1931 state.on_advance(&advance_msg);
1932 assert_eq!(state, ComponentState::locked_in(advance_msg.now));
1933
1934 let after_locked_in_ts = now.saturating_add(MassaTime::from_millis(10));
1937 let state_id = state
1938 .state_at(
1939 after_locked_in_ts,
1940 mi.start,
1941 mi.timeout,
1942 mi.activation_delay,
1943 )
1944 .unwrap();
1945 assert_eq!(state_id, ComponentStateTypeId::LockedIn);
1946 let state_id = state
1948 .state_at(after_timeout_ts, mi.start, mi.timeout, mi.activation_delay)
1949 .unwrap();
1950 assert_eq!(state_id, ComponentStateTypeId::Active);
1951 }
1952
1953 #[test]
1954 fn test_versioning_store_announce_current() {
1955 let (start, timeout, mi) = get_a_version_info();
1958
1959 let mut mi_2 = mi.clone();
1960 mi_2.version += 1;
1961 mi_2.start = timeout
1962 .checked_add(MassaTime::from_millis(1000 * 60 * 60 * 24 * 2))
1963 .unwrap(); mi_2.timeout = timeout
1965 .checked_add(MassaTime::from_millis(1000 * 60 * 60 * 24 * 5))
1966 .unwrap(); let vs_1 = MipState {
1970 state: ComponentState::active(start),
1971 history: Default::default(),
1972 };
1973 let vs_2 = MipState {
1974 state: ComponentState::started(Ratio::zero()),
1975 history: Default::default(),
1976 };
1977
1978 let mip_stats_cfg = MipStatsConfig {
1980 block_count_considered: 10,
1981 warn_announced_version_ratio: Ratio::new_raw(30, 100),
1982 };
1983 let vs_raw = MipStoreRaw {
1984 store: BTreeMap::from([(mi.clone(), vs_1), (mi_2.clone(), vs_2)]),
1985 stats: MipStoreStats::new(mip_stats_cfg.clone()),
1986 };
1987 let vs = MipStore(Arc::new(RwLock::new(vs_raw)));
1989
1990 assert_eq!(vs.get_network_version_current(), mi.version);
1991 assert_eq!(vs.get_network_version_to_announce(), Some(mi_2.version));
1992
1993 let vs_raw = MipStoreRaw {
1995 store: Default::default(),
1996 stats: MipStoreStats::new(mip_stats_cfg),
1997 };
1998 let vs = MipStore(Arc::new(RwLock::new(vs_raw)));
1999 assert_eq!(vs.get_network_version_current(), 0);
2000 assert_eq!(vs.get_network_version_to_announce(), None);
2001 }
2002
2003 #[test]
2004 fn test_is_consistent_with() {
2005 let vi_1 = MipInfo {
2013 name: "MIP-0002".to_string(),
2014 version: 2,
2015 components: BTreeMap::from([(MipComponent::Address, 1)]),
2016 start: MassaTime::from_millis(2),
2017 timeout: MassaTime::from_millis(5),
2018 activation_delay: MassaTime::from_millis(2),
2019 };
2020 let vi_2 = MipInfo {
2022 name: "MIP-0002".to_string(),
2023 version: 2,
2024 components: BTreeMap::from([(MipComponent::Address, 1)]),
2025 start: MassaTime::from_millis(7),
2026 timeout: MassaTime::from_millis(10),
2027 activation_delay: MassaTime::from_millis(2),
2028 };
2029
2030 let vsh = MipState {
2031 state: ComponentState::Error,
2032 history: Default::default(),
2033 };
2034 assert_eq!(
2036 vsh.is_consistent_with(&vi_1),
2037 Err(IsConsistentError::AtError)
2038 );
2039
2040 let vsh = MipState {
2041 state: ComponentState::defined(),
2042 history: Default::default(),
2043 };
2044 assert!(vsh.is_consistent_with(&vi_1).is_err());
2046
2047 let mut vsh = MipState::new(MassaTime::from_millis(1));
2048 assert!(vsh.is_consistent_with(&vi_1).is_ok());
2050 let now = MassaTime::from_millis(3);
2055 let adv = Advance::from((&vi_1, &Ratio::zero(), &now));
2056 vsh.on_advance(&adv);
2057 let now = MassaTime::from_millis(4);
2058 let adv = Advance::from((&vi_1, &Ratio::new_raw(14, 100), &now));
2059 vsh.on_advance(&adv);
2060
2061 assert_eq!(vsh.state, ComponentState::started(Ratio::new_raw(14, 100)));
2063 assert!(vsh.is_consistent_with(&vi_1).is_ok());
2064 assert!(vsh.is_consistent_with(&vi_2).is_err());
2066
2067 let now = MassaTime::from_millis(4);
2069 let adv = Advance::from((&vi_1, &VERSIONING_THRESHOLD_TRANSITION_ACCEPTED, &now));
2070 vsh.on_advance(&adv);
2071
2072 assert_eq!(vsh.state, ComponentState::locked_in(now));
2074 assert!(vsh.is_consistent_with(&vi_1).is_ok());
2075
2076 }
2079
2080 #[test]
2081 fn test_update_with() {
2082 let vi_1 = MipInfo {
2085 name: "MIP-0002".to_string(),
2086 version: 2,
2087 components: BTreeMap::from([(MipComponent::Address, 1)]),
2088 start: MassaTime::from_millis(2),
2089 timeout: MassaTime::from_millis(5),
2090 activation_delay: MassaTime::from_millis(2),
2091 };
2092
2093 let _time = MassaTime::now();
2094 let vs_1 = advance_state_until(ComponentState::active(_time), &vi_1);
2095 assert!(matches!(vs_1.state, ComponentState::Active(_)));
2096
2097 let vi_2 = MipInfo {
2098 name: "MIP-0003".to_string(),
2099 version: 3,
2100 components: BTreeMap::from([(MipComponent::Address, 2)]),
2101 start: MassaTime::from_millis(17),
2102 timeout: MassaTime::from_millis(27),
2103 activation_delay: MassaTime::from_millis(2),
2104 };
2105 let vs_2 = advance_state_until(ComponentState::defined(), &vi_2);
2106
2107 let mip_stats_cfg = MipStatsConfig {
2108 block_count_considered: 10,
2109 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2110 };
2111 let mut vs_raw_1 = MipStoreRaw::try_from((
2112 [(vi_1.clone(), vs_1.clone()), (vi_2.clone(), vs_2.clone())],
2113 mip_stats_cfg.clone(),
2114 ))
2115 .unwrap();
2116
2117 let vs_2_2 = advance_state_until(ComponentState::active(_time), &vi_2);
2118 assert!(matches!(vs_2_2.state, ComponentState::Active(_)));
2119
2120 let vs_raw_2 = MipStoreRaw::try_from((
2121 [(vi_1.clone(), vs_1.clone()), (vi_2.clone(), vs_2_2.clone())],
2122 mip_stats_cfg,
2123 ))
2124 .unwrap();
2125
2126 let (updated, added) = vs_raw_1.update_with(&vs_raw_2).unwrap();
2127
2128 assert!(added.is_empty());
2130 assert_eq!(updated, vec![vi_2.clone()]);
2131
2132 assert_eq!(vs_raw_1.store.get(&vi_1).unwrap().state, vs_1.state);
2134 assert_eq!(vs_raw_1.store.get(&vi_2).unwrap().state, vs_2_2.state);
2135 }
2136
2137 #[test]
2138 fn test_update_with_invalid() {
2139 let mi_1 = MipInfo {
2143 name: "MIP-0001".to_string(),
2144 version: 2,
2145 components: BTreeMap::from([(MipComponent::Address, 1)]),
2146 start: MassaTime::from_millis(0),
2147 timeout: MassaTime::from_millis(5),
2148 activation_delay: MassaTime::from_millis(2),
2149 };
2150 let _time = MassaTime::now();
2151 let ms_1 = advance_state_until(ComponentState::active(_time), &mi_1);
2152 assert!(matches!(ms_1.state, ComponentState::Active(_)));
2153
2154 let mi_2 = MipInfo {
2155 name: "MIP-0002".to_string(),
2156 version: 3,
2157 components: BTreeMap::from([(MipComponent::Address, 2)]),
2158 start: MassaTime::from_millis(17),
2159 timeout: MassaTime::from_millis(27),
2160 activation_delay: MassaTime::from_millis(2),
2161 };
2162 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2163 assert_eq!(ms_2, ComponentState::defined());
2164
2165 let mip_stats_cfg = MipStatsConfig {
2166 block_count_considered: 10,
2167 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2168 };
2169
2170 {
2172 let mut store_1 = MipStoreRaw::try_from((
2173 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2174 mip_stats_cfg.clone(),
2175 ))
2176 .unwrap();
2177
2178 let mut mi_2_2 = mi_2.clone();
2179 mi_2_2.start = mi_1.timeout;
2181 let ms_2_2 = advance_state_until(ComponentState::defined(), &mi_2_2);
2182 let store_2 = MipStoreRaw {
2183 store: BTreeMap::from([
2184 (mi_1.clone(), ms_1.clone()),
2185 (mi_2_2.clone(), ms_2_2.clone()),
2186 ]),
2187 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2188 };
2189
2190 assert_matches!(
2191 store_1.update_with(&store_2),
2192 Err(UpdateWithError::Overlapping(..))
2193 );
2194 assert_eq!(store_1.store.get(&mi_1).unwrap().state, ms_1.state);
2195 assert_eq!(store_1.store.get(&mi_2).unwrap().state, ms_2.state);
2196
2197 {
2199 let _store_2_ = MipStoreRaw::try_from((
2200 [
2201 (mi_1.clone(), ms_1.clone()),
2202 (mi_2_2.clone(), ms_2_2.clone()),
2203 ],
2204 mip_stats_cfg.clone(),
2205 ));
2206 assert!(_store_2_.is_err());
2207 }
2208 }
2209
2210 {
2212 let mut store_1 = MipStoreRaw::try_from((
2213 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2214 mip_stats_cfg.clone(),
2215 ))
2216 .unwrap();
2217
2218 let mut mi_2_2 = mi_2.clone();
2219 mi_2_2.components = mi_1.components.clone();
2221
2222 let ms_2_2 = advance_state_until(ComponentState::defined(), &mi_2_2);
2223 let store_2 = MipStoreRaw {
2224 store: BTreeMap::from([
2225 (mi_1.clone(), ms_1.clone()),
2226 (mi_2_2.clone(), ms_2_2.clone()),
2227 ]),
2228 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2229 };
2230
2231 assert_matches!(
2233 store_1.update_with(&store_2),
2234 Err(UpdateWithError::Overlapping(..))
2235 );
2236 }
2237
2238 {
2240 let mut store_1 =
2241 MipStoreRaw::try_from(([(mi_1.clone(), ms_1.clone())], mip_stats_cfg.clone()))
2242 .unwrap();
2243 let mut mi_2_2 = mi_2.clone();
2244 mi_2_2.version = mi_1.version - 1;
2246
2247 let store_2 = MipStoreRaw {
2248 store: BTreeMap::from([(mi_2_2.clone(), ms_2.clone())]),
2249 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2250 };
2251
2252 assert_matches!(
2253 store_1.update_with(&store_2),
2254 Err(UpdateWithError::Overlapping(..))
2255 );
2256
2257 let mut mi_2_3 = mi_2.clone();
2260 mi_2_3.version = mi_1.version;
2262
2263 let store_2 = MipStoreRaw {
2264 store: BTreeMap::from([(mi_2_3.clone(), ms_2.clone())]),
2265 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2266 };
2267
2268 assert_matches!(
2269 store_1.update_with(&store_2),
2270 Err(UpdateWithError::Overlapping(..))
2271 );
2272 }
2273
2274 {
2276 let mut store_1 =
2277 MipStoreRaw::try_from(([(mi_1.clone(), ms_1.clone())], mip_stats_cfg.clone()))
2278 .unwrap();
2279 let mut mi_2_2 = mi_2.clone();
2280 mi_2_2.name = mi_1.name.clone();
2282
2283 let store_2 = MipStoreRaw {
2284 store: BTreeMap::from([(mi_2_2.clone(), ms_2.clone())]),
2285 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2286 };
2287
2288 assert_matches!(
2289 store_1.update_with(&store_2),
2290 Err(UpdateWithError::Overlapping(..))
2291 );
2292 }
2293
2294 {
2296 let ms_1_1 =
2297 advance_state_until(ComponentState::locked_in(MassaTime::from_millis(0)), &mi_1);
2298 let ms_1_2 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2299 let mut store_1 =
2300 MipStoreRaw::try_from(([(mi_1.clone(), ms_1_1.clone())], mip_stats_cfg.clone()))
2301 .unwrap();
2302
2303 let store_2 = MipStoreRaw {
2305 store: BTreeMap::from([(mi_1.clone(), ms_1_2.clone())]),
2306 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2307 };
2308
2309 assert_matches!(
2310 store_1.update_with(&store_2),
2311 Err(UpdateWithError::Downgrade(..))
2312 );
2313 }
2314 }
2315
2316 #[test]
2317 fn test_try_from_invalid() {
2318 let mip_stats_cfg = MipStatsConfig {
2322 block_count_considered: 10,
2323 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2324 };
2325 let mi_1 = MipInfo {
2326 name: "MIP-0002".to_string(),
2327 version: 2,
2328 components: BTreeMap::from([(MipComponent::Address, 1)]),
2329 start: MassaTime::from_millis(0),
2330 timeout: MassaTime::from_millis(5),
2331 activation_delay: MassaTime::from_millis(2),
2332 };
2333 let _time = MassaTime::now();
2334 let ms_1 = advance_state_until(ComponentState::active(_time), &mi_1);
2335 assert!(matches!(ms_1.state, ComponentState::Active(_)));
2336 {
2337 let mut mi_1_1 = mi_1.clone();
2339 mi_1_1.start = MassaTime::from_millis(5);
2340 mi_1_1.timeout = MassaTime::from_millis(2);
2341
2342 let mip_store =
2343 MipStoreRaw::try_from(([(mi_1_1, ms_1.clone())], mip_stats_cfg.clone()));
2344 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2345 }
2346 {
2347 let ms_1_2 = MipState::new(MassaTime::from_millis(15));
2348 let mut mi_1_2 = mi_1.clone();
2350 mi_1_2.start = MassaTime::from_millis(2);
2351 mi_1_2.timeout = MassaTime::from_millis(5);
2352
2353 let mip_store = MipStoreRaw::try_from(([(mi_1_2, ms_1_2)], mip_stats_cfg.clone()));
2354 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2355 }
2356 {
2357 let ms_1_2 = MipState::new(MassaTime::from_millis(15));
2358 let mut mi_1_2 = mi_1.clone();
2360 mi_1_2.start = MassaTime::from_millis(16);
2361 mi_1_2.timeout = MassaTime::from_millis(5);
2362
2363 let mip_store = MipStoreRaw::try_from(([(mi_1_2, ms_1_2)], mip_stats_cfg));
2364 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2365 }
2366 }
2367
2368 #[test]
2369 fn test_empty_mip_store() {
2370 let mip_stats_config = MipStatsConfig {
2373 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2374 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2375 };
2376
2377 let mip_store = MipStore::try_from(([], mip_stats_config));
2378 assert!(mip_store.is_ok());
2379 }
2380
2381 #[test]
2382 fn test_update_with_unknown() {
2383 let mip_stats_config = MipStatsConfig {
2387 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2388 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2389 };
2390
2391 let mut mip_store_raw_1 = MipStoreRaw::try_from(([], mip_stats_config.clone())).unwrap();
2392
2393 let mi_1 = MipInfo {
2394 name: "MIP-0002".to_string(),
2395 version: 2,
2396 components: BTreeMap::from([(MipComponent::__Nonexhaustive, 1)]),
2397 start: MassaTime::from_millis(0),
2398 timeout: MassaTime::from_millis(5),
2399 activation_delay: MassaTime::from_millis(2),
2400 };
2401 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2402 assert_eq!(ms_1, ComponentState::defined());
2403 let mip_store_raw_2 = MipStoreRaw {
2404 store: BTreeMap::from([(mi_1.clone(), ms_1.clone())]),
2405 stats: MipStoreStats::new(mip_stats_config.clone()),
2406 };
2407
2408 let (updated, added) = mip_store_raw_1.update_with(&mip_store_raw_2).unwrap();
2409
2410 assert_eq!(updated.len(), 0);
2411 assert_eq!(added.len(), 1);
2412 assert_eq!(added.get(&mi_1).unwrap().state, ComponentState::defined());
2413 }
2414
2415 #[test]
2416 fn test_mip_store_network_restart() {
2417 let genesis_timestamp = MassaTime::from_millis(0);
2420
2421 let get_slot_ts =
2423 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2424 let is_consistent = |store: &MipStoreRaw, shutdown_start, shutdown_end| {
2425 store.is_consistent_with_shutdown_period(
2426 shutdown_start,
2427 shutdown_end,
2428 THREAD_COUNT,
2429 T0,
2430 genesis_timestamp,
2431 )
2432 };
2433 let update_store = |store: &mut MipStoreRaw, shutdown_start, shutdown_end| {
2434 store
2435 .update_for_network_shutdown(
2436 shutdown_start,
2437 shutdown_end,
2438 THREAD_COUNT,
2439 T0,
2440 genesis_timestamp,
2441 )
2442 .unwrap()
2443 };
2444 let _dump_store = |store: &MipStoreRaw| {
2445 println!("Dump store:");
2446 for (mip_info, mip_state) in store.store.iter() {
2447 println!(
2448 "mip_info {} {} - start: {} - timeout: {}: state: {:?}",
2449 mip_info.name,
2450 mip_info.version,
2451 get_closest_slot_to_timestamp(
2452 THREAD_COUNT,
2453 T0,
2454 genesis_timestamp,
2455 mip_info.start
2456 ),
2457 get_closest_slot_to_timestamp(
2458 THREAD_COUNT,
2459 T0,
2460 genesis_timestamp,
2461 mip_info.timeout
2462 ),
2463 mip_state.state
2464 );
2465 }
2466 };
2467 let shutdown_start = Slot::new(2, 0);
2470 let shutdown_end = Slot::new(8, 0);
2471
2472 let mip_stats_cfg = MipStatsConfig {
2473 block_count_considered: 10,
2474 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2475 };
2476 let mut mi_1 = MipInfo {
2477 name: "MIP-0002".to_string(),
2478 version: 2,
2479 components: BTreeMap::from([(MipComponent::Address, 1)]),
2480 start: MassaTime::from_millis(2),
2481 timeout: MassaTime::from_millis(5),
2482 activation_delay: MassaTime::from_millis(100),
2483 };
2484 let mut mi_2 = MipInfo {
2485 name: "MIP-0003".to_string(),
2486 version: 3,
2487 components: BTreeMap::from([(MipComponent::Address, 2)]),
2488 start: MassaTime::from_millis(7),
2489 timeout: MassaTime::from_millis(11),
2490 activation_delay: MassaTime::from_millis(100),
2491 };
2492
2493 {
2495 mi_1.start = get_slot_ts(Slot::new(3, 7));
2496 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2497 mi_2.start = get_slot_ts(Slot::new(7, 7));
2498 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2499
2500 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2501 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2502 assert_eq!(ms_1, ComponentState::defined());
2503 assert_eq!(ms_2, ComponentState::defined());
2504
2505 let mut store = MipStoreRaw::try_from((
2506 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2507 mip_stats_cfg.clone(),
2508 ))
2509 .unwrap();
2510
2511 match is_consistent(&store, shutdown_start, shutdown_end) {
2512 Err(IsConsistentWithShutdownPeriodError::NonConsistent(mi, ..)) => {
2513 assert_eq!(mi, mi_1);
2514 }
2515 _ => panic!("is_consistent expects a non consistent error"),
2516 }
2517
2518 update_store(&mut store, shutdown_start, shutdown_end);
2519 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2520 }
2522
2523 {
2525 mi_1.start = get_slot_ts(Slot::new(9, 7));
2526 mi_1.timeout = get_slot_ts(Slot::new(11, 7));
2527 mi_2.start = get_slot_ts(Slot::new(12, 7));
2528 mi_2.timeout = get_slot_ts(Slot::new(19, 7));
2529
2530 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2531 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2532 assert_eq!(ms_1, ComponentState::defined());
2533 assert_eq!(ms_2, ComponentState::defined());
2534 let mut store = MipStoreRaw::try_from((
2535 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2536 mip_stats_cfg.clone(),
2537 ))
2538 .unwrap();
2539 let store_orig = store.clone();
2540
2541 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2543 update_store(&mut store, shutdown_start, shutdown_end);
2545 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2546 assert_eq!(store_orig, store);
2550 }
2551
2552 {
2554 mi_1.start = get_slot_ts(Slot::new(1, 7));
2555 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2556 mi_2.start = get_slot_ts(Slot::new(7, 7));
2557 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2558
2559 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2560 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2561 assert_eq!(ms_1, ComponentState::started(Ratio::zero()));
2562 assert_eq!(ms_2, ComponentState::defined());
2563 let mut store = MipStoreRaw::try_from((
2564 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2565 mip_stats_cfg.clone(),
2566 ))
2567 .unwrap();
2568
2569 assert_matches!(
2570 is_consistent(&store, shutdown_start, shutdown_end),
2571 Err(IsConsistentWithShutdownPeriodError::NonConsistent(..))
2572 );
2573 update_store(&mut store, shutdown_start, shutdown_end);
2574 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2575 }
2577
2578 {
2580 mi_1.start = get_slot_ts(Slot::new(9, 2));
2581 mi_1.timeout = get_slot_ts(Slot::new(10, 7));
2582 mi_2.start = get_slot_ts(Slot::new(11, 4));
2583 mi_2.timeout = get_slot_ts(Slot::new(12, 0));
2584
2585 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2586 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2587 assert_eq!(ms_1, ComponentState::started(Ratio::zero()));
2588 assert_eq!(ms_2, ComponentState::defined());
2589 let mut store = MipStoreRaw::try_from((
2590 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2591 mip_stats_cfg.clone(),
2592 ))
2593 .unwrap();
2594
2595 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2596 update_store(&mut store, shutdown_start, shutdown_end);
2597 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2598 }
2600
2601 {
2603 let shutdown_range = shutdown_start..=shutdown_end;
2604
2605 mi_1.start = get_slot_ts(Slot::new(1, 7));
2606 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2607
2608 let locked_in_at = Slot::new(1, 9);
2610 assert!(locked_in_at < shutdown_start);
2611 let activate_at = Slot::new(4, 0);
2612 assert!(shutdown_range.contains(&activate_at));
2613 mi_1.activation_delay =
2615 get_slot_ts(activate_at).saturating_sub(get_slot_ts(locked_in_at));
2616 let ms_1 = advance_state_until(
2617 ComponentState::locked_in(get_slot_ts(Slot::new(1, 9))),
2618 &mi_1,
2619 );
2620
2621 mi_2.start = get_slot_ts(Slot::new(7, 7));
2623 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2624 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2625 let mut store = MipStoreRaw::try_from((
2626 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2627 mip_stats_cfg.clone(),
2628 ))
2629 .unwrap();
2630
2631 match is_consistent(&store, shutdown_start, shutdown_end) {
2632 Err(IsConsistentWithShutdownPeriodError::NonConsistent(mi, ..)) => {
2633 assert_eq!(mi, mi_1);
2634 }
2635 _ => panic!("is_consistent expects a non consistent error"),
2636 }
2637 update_store(&mut store, shutdown_start, shutdown_end);
2639 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2640 store.update_network_version_stats(
2644 get_slot_ts(shutdown_end.get_next_slot(THREAD_COUNT).unwrap()),
2645 Some((1, None)),
2646 );
2647
2648 let (first_mi_info, first_mi_state) = store.store.first_key_value().unwrap();
2649 assert_eq!(*first_mi_info.name, mi_1.name);
2650 assert_eq!(
2652 ComponentStateTypeId::from(&first_mi_state.state),
2653 ComponentStateTypeId::Started
2654 );
2655 let (last_mi_info, last_mi_state) = store.store.last_key_value().unwrap();
2656 assert_eq!(*last_mi_info.name, mi_2.name);
2657 assert_eq!(
2659 ComponentStateTypeId::from(&last_mi_state.state),
2660 ComponentStateTypeId::Defined
2661 );
2662 }
2663 }
2664
2665 #[test]
2666 fn test_started_mips_missing_from_db() {
2667 let genesis_timestamp = MassaTime::from_millis(0);
2672 let get_slot_ts =
2673 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2674
2675 let temp_dir = tempdir().expect("Unable to create a temp folder");
2676 let db_config = MassaDBConfig {
2677 path: temp_dir.path().to_path_buf(),
2678 max_history_length: 100,
2679 max_final_state_elements_size: 100_000,
2680 max_versioning_elements_size: 100_000,
2681 thread_count: THREAD_COUNT,
2682 max_ledger_backups: 10,
2683 enable_metrics: false,
2684 };
2685 let db = Arc::new(RwLock::new(
2686 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2687 ));
2688
2689 let mi_old = MipInfo {
2691 name: "MIP-0002".to_string(),
2692 version: 2,
2693 components: BTreeMap::from([(MipComponent::Address, 1)]),
2694 start: get_slot_ts(Slot::new(2, 0)),
2695 timeout: get_slot_ts(Slot::new(3, 0)),
2696 activation_delay: MassaTime::from_millis(10),
2697 };
2698 let ms_old = advance_state_until(
2699 ComponentState::active(get_slot_ts(Slot::new(2, 5))),
2700 &mi_old,
2701 );
2702 let mi_new = MipInfo {
2704 name: "MIP-0003".to_string(),
2705 version: 3,
2706 components: BTreeMap::from([(MipComponent::Address, 2)]),
2707 start: get_slot_ts(Slot::new(4, 2)),
2708 timeout: get_slot_ts(Slot::new(7, 2)),
2709 activation_delay: MassaTime::from_millis(10),
2710 };
2711 let ms_new = advance_state_until(ComponentState::defined(), &mi_new);
2712
2713 let mip_stats_config = MipStatsConfig {
2714 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2715 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2716 };
2717
2718 let server_store =
2720 MipStore::try_from(([(mi_old.clone(), ms_old)], mip_stats_config.clone()))
2721 .expect("Cannot create the server MIP store");
2722 let mut db_batch = DBBatch::new();
2723 let mut db_versioning_batch = DBBatch::new();
2724 server_store
2725 .update_batches(&mut db_batch, &mut db_versioning_batch, None)
2726 .unwrap();
2727 db.write()
2728 .write_batch(db_batch, db_versioning_batch, Some(Slot::new(3, 0)));
2729
2730 let client_store = MipStore::try_from((
2731 [
2732 (
2733 mi_old.clone(),
2734 advance_state_until(ComponentState::defined(), &mi_old),
2735 ),
2736 (mi_new.clone(), ms_new),
2737 ],
2738 mip_stats_config,
2739 ))
2740 .expect("Cannot create the client MIP store");
2741
2742 let before_start = get_slot_ts(Slot::new(4, 1));
2744 assert!(client_store
2745 .started_mips_missing_from_db(&db, before_start)
2746 .is_empty());
2747
2748 for at in [mi_new.start, get_slot_ts(Slot::new(5, 0))] {
2750 assert_eq!(
2751 client_store.started_mips_missing_from_db(&db, at),
2752 vec![mi_new.clone()]
2753 );
2754 }
2755
2756 assert!(!client_store
2758 .started_mips_missing_from_db(&db, get_slot_ts(Slot::new(9, 0)))
2759 .contains(&mi_old));
2760 }
2761
2762 #[test]
2763 fn test_mip_store_db() {
2764 let genesis_timestamp = MassaTime::from_millis(0);
2772 let get_slot_ts =
2774 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2775
2776 let temp_dir = tempdir().expect("Unable to create a temp folder");
2779 let db_config = MassaDBConfig {
2782 path: temp_dir.path().to_path_buf(),
2783 max_history_length: 100,
2784 max_final_state_elements_size: 100_000,
2785 max_versioning_elements_size: 100_000,
2786 thread_count: THREAD_COUNT,
2787 max_ledger_backups: 10,
2788 enable_metrics: false,
2789 };
2790 let db = Arc::new(RwLock::new(
2791 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2792 ));
2793
2794 let mi_1 = MipInfo {
2797 name: "MIP-0002".to_string(),
2798 version: 2,
2799 components: BTreeMap::from([(MipComponent::Address, 1)]),
2800 start: get_slot_ts(Slot::new(2, 0)),
2801 timeout: get_slot_ts(Slot::new(3, 0)),
2802 activation_delay: MassaTime::from_millis(10),
2803 };
2804 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2805
2806 let mi_2 = MipInfo {
2807 name: "MIP-0003".to_string(),
2808 version: 3,
2809 components: BTreeMap::from([(MipComponent::Address, 2)]),
2810 start: get_slot_ts(Slot::new(4, 2)),
2811 timeout: get_slot_ts(Slot::new(7, 2)),
2812 activation_delay: MassaTime::from_millis(10),
2813 };
2814 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2815
2816 let mip_stats_config = MipStatsConfig {
2817 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2818 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2819 };
2820 let mut mip_store = MipStore::try_from((
2821 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2822 mip_stats_config.clone(),
2823 ))
2824 .expect("Cannot create an empty MIP store");
2825
2826 mip_store.extend_from_db(db.clone()).unwrap();
2829 assert_eq!(mip_store.0.read().store.len(), 2);
2831 assert_eq!(
2832 mip_store.0.read().store.first_key_value(),
2833 Some((&mi_1, &ms_1))
2834 );
2835 assert_eq!(
2836 mip_store.0.read().store.last_key_value(),
2837 Some((&mi_2, &ms_2))
2838 );
2839
2840 let active_at = get_slot_ts(Slot::new(2, 5));
2842 let ms_1_ = advance_state_until(ComponentState::active(active_at), &mi_1);
2843
2844 let mip_store_ =
2845 MipStore::try_from(([(mi_1.clone(), ms_1_.clone())], mip_stats_config.clone()))
2846 .expect("Cannot create an empty MIP store");
2847
2848 let (updated, added) = mip_store.update_with(&mip_store_).unwrap();
2849
2850 assert_eq!(updated.len(), 1);
2852 assert_eq!(added.len(), 0);
2853 assert_eq!(mip_store.0.read().store.len(), 2);
2854 assert_eq!(
2855 mip_store.0.read().store.first_key_value(),
2856 Some((&mi_1, &ms_1_))
2857 );
2858 assert_eq!(
2859 mip_store.0.read().store.last_key_value(),
2860 Some((&mi_2, &ms_2))
2861 );
2862
2863 let mut db_batch = DBBatch::new();
2866 let mut db_versioning_batch = DBBatch::new();
2867
2868 let slot_bounds_ = (&Slot::new(1, 0), &Slot::new(4, 2));
2870 let between = (&get_slot_ts(*slot_bounds_.0), &get_slot_ts(*slot_bounds_.1));
2871
2872 mip_store
2873 .update_batches(&mut db_batch, &mut db_versioning_batch, Some(between))
2874 .unwrap();
2875
2876 assert_eq!(db_batch.len(), 1); assert_eq!(db_versioning_batch.len(), 3); let mut guard_db = db.write();
2880 guard_db.write_batch(db_batch, db_versioning_batch, Some(Slot::new(3, 0)));
2882 drop(guard_db);
2883
2884 let mut mip_store_2 = MipStore::try_from((
2886 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2887 mip_stats_config.clone(),
2888 ))
2889 .expect("Cannot create an empty MIP store");
2890 mip_store_2.extend_from_db(db.clone()).unwrap();
2893
2894 let guard_1 = mip_store.0.read();
2895 let guard_2 = mip_store_2.0.read();
2896 let st1_raw = guard_1.deref();
2897 let st2_raw = guard_2.deref();
2898
2899 assert_eq!(st1_raw, st2_raw);
2902
2903 let mut count = 0;
2905 for (ser_key, ser_value) in db
2906 .read()
2907 .iterator_cf_for_full_db_traversal(STATE_CF, MassaIteratorMode::Start)
2908 {
2909 assert!(mip_store.is_key_value_valid(&ser_key, &ser_value));
2910 count += 1;
2911 }
2912 assert_gt!(count, 0);
2913
2914 let mut count2 = 0;
2915 for (ser_key, ser_value) in db
2916 .read()
2917 .iterator_cf_for_full_db_traversal(VERSIONING_CF, MassaIteratorMode::Start)
2918 {
2919 assert!(mip_store.is_key_value_valid(&ser_key, &ser_value));
2920 count2 += 1;
2921 }
2922 assert_gt!(count2, 0);
2923 }
2924
2925 #[test]
2926 fn test_mip_store_stats() {
2927 let genesis_timestamp = MassaTime::from_millis(0);
2931 let get_slot_ts =
2932 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2933
2934 let mip_stats_config = MipStatsConfig {
2935 block_count_considered: 2,
2936 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2937 };
2938 let activation_delay = MassaTime::from_millis(100);
2939 let timeout = MassaTime::now().saturating_add(MassaTime::from_millis(50_000)); let mi_1 = MipInfo {
2941 name: "MIP-0001".to_string(),
2942 version: 1,
2943 components: BTreeMap::from([(MipComponent::Address, 1)]),
2944 start: MassaTime::from_millis(2),
2945 timeout,
2946 activation_delay,
2947 };
2948 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2949
2950 let mut mip_store =
2951 MipStoreRaw::try_from(([(mi_1.clone(), ms_1)], mip_stats_config)).unwrap();
2952
2953 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((0, Some(1))));
2955 assert_eq!(mip_store.stats.network_version_counters.len(), 1);
2956 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&1));
2957
2958 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((0, Some(1))));
2959 assert_eq!(mip_store.stats.network_version_counters.len(), 1);
2960 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&2));
2961
2962 let (mi_, ms_) = mip_store.store.last_key_value().unwrap();
2964 assert_eq!(*mi_, mi_1);
2965 assert_matches!(ms_.state, ComponentState::LockedIn(..));
2966
2967 let mut at = MassaTime::now();
2968 at = at.saturating_add(activation_delay);
2969 assert_eq!(
2970 ms_.state_at(at, mi_1.start, mi_1.timeout, mi_1.activation_delay),
2971 Ok(ComponentStateTypeId::Active)
2972 );
2973
2974 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((1, Some(2))));
2976 assert_eq!(mip_store.stats.network_version_counters.len(), 2);
2978 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&1));
2980 assert_eq!(mip_store.stats.network_version_counters.get(&2), Some(&1));
2981 }
2982
2983 #[test]
2987 fn test_get_network_version_active_at_locked_in_matches_active() {
2988 let mi = MipInfo {
2989 name: "MIP-test-LockedIn-net".to_string(),
2990 version: 2,
2991 components: BTreeMap::from([(MipComponent::Execution, 2)]),
2992 start: MassaTime::from_millis(10_000),
2993 timeout: MassaTime::from_millis(100_000),
2994 activation_delay: MassaTime::from_millis(5_000),
2995 };
2996 let stats = MipStatsConfig {
2997 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2998 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2999 };
3000 let store_locked = MipStore::try_from((
3001 [(
3002 mi.clone(),
3003 advance_state_until(ComponentState::locked_in(MassaTime::from_millis(0)), &mi),
3004 )],
3005 stats.clone(),
3006 ))
3007 .expect("locked store");
3008 let store_active = MipStore::try_from((
3009 [(
3010 mi.clone(),
3011 advance_state_until(ComponentState::active(MassaTime::from_millis(0)), &mi),
3012 )],
3013 stats,
3014 ))
3015 .expect("active store");
3016
3017 let locked_in_at = match store_locked.0.read().store.get(&mi).unwrap().state {
3018 ComponentState::LockedIn(LockedIn { at }) => at,
3019 other => panic!("expected LockedIn, got {:?}", other),
3020 };
3021 let instant = locked_in_at.saturating_add(mi.activation_delay);
3022 let after = instant.saturating_add(MassaTime::from_millis(1));
3023
3024 for ts in [locked_in_at, instant] {
3026 assert_eq!(store_locked.get_network_version_active_at(ts), 0);
3027 assert_eq!(store_active.get_network_version_active_at(ts), 0);
3028 assert_eq!(store_locked.get_network_version_to_announce_at(ts), Some(2));
3029 }
3030 assert_eq!(store_locked.get_network_version_active_at(after), 2);
3032 assert_eq!(store_active.get_network_version_active_at(after), 2);
3033 assert_eq!(store_locked.get_network_version_to_announce_at(after), None);
3034 assert_eq!(store_active.get_network_version_to_announce_at(after), None);
3035
3036 assert_eq!(store_locked.get_network_version_current(), 0);
3038 assert_eq!(store_locked.get_network_version_to_announce(), Some(2));
3039 }
3040
3041 #[test]
3045 fn test_header_versions_at_are_always_consistent() {
3046 let mi = MipInfo {
3047 name: "MIP-test-header".to_string(),
3048 version: 2,
3049 components: BTreeMap::from([(MipComponent::Execution, 2)]),
3050 start: MassaTime::from_millis(10_000),
3051 timeout: MassaTime::from_millis(100_000),
3052 activation_delay: MassaTime::from_millis(5_000),
3053 };
3054 let stats = MipStatsConfig {
3055 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
3056 warn_announced_version_ratio: Ratio::new_raw(30, 100),
3057 };
3058 for state in [
3059 ComponentState::started(Ratio::zero()),
3060 ComponentState::locked_in(MassaTime::from_millis(0)),
3061 ComponentState::active(MassaTime::from_millis(0)),
3062 ] {
3063 let store = MipStore::try_from((
3064 [(mi.clone(), advance_state_until(state, &mi))],
3065 stats.clone(),
3066 ))
3067 .expect("store");
3068 for ts in (0..40_000u64).step_by(500).map(MassaTime::from_millis) {
3069 let current = store.get_network_version_active_at(ts);
3070 if let Some(announced) = store.get_network_version_to_announce_at(ts) {
3071 assert!(
3072 announced > current,
3073 "ts {}: announced {} <= current {}",
3074 ts.as_millis(),
3075 announced,
3076 current
3077 );
3078 }
3079 }
3080 }
3081 }
3082
3083 #[test]
3086 fn test_get_latest_component_version_at_locked_in_matches_active() {
3087 let start = MassaTime::from_millis(10_000);
3088 let timeout = MassaTime::from_millis(100_000);
3089 let activation_delay = MassaTime::from_millis(5_000);
3090 let mi = MipInfo {
3091 name: "MIP-test-LockedIn".to_string(),
3092 version: 2,
3093 components: BTreeMap::from([(MipComponent::Execution, 2)]),
3094 start,
3095 timeout,
3096 activation_delay,
3097 };
3098 let stats = MipStatsConfig {
3099 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
3100 warn_announced_version_ratio: Ratio::new_raw(30, 100),
3101 };
3102
3103 let ms_locked =
3104 advance_state_until(ComponentState::locked_in(MassaTime::from_millis(0)), &mi);
3105 assert_matches!(ms_locked.state, ComponentState::LockedIn(_));
3106 let store_locked =
3107 MipStore::try_from(([(mi.clone(), ms_locked)], stats.clone())).expect("locked store");
3108
3109 let ms_active = advance_state_until(ComponentState::active(MassaTime::from_millis(0)), &mi);
3110 assert_matches!(ms_active.state, ComponentState::Active(_));
3111 let store_active =
3112 MipStore::try_from(([(mi.clone(), ms_active)], stats)).expect("active store");
3113
3114 let locked_in_at = match store_locked.0.read().store.get(&mi).unwrap().state {
3115 ComponentState::LockedIn(LockedIn { at }) => at,
3116 other => panic!("expected LockedIn, got {:?}", other),
3117 };
3118 let before = locked_in_at
3119 .saturating_add(activation_delay)
3120 .saturating_sub(MassaTime::from_millis(1));
3121 let after = locked_in_at
3123 .saturating_add(activation_delay)
3124 .saturating_add(MassaTime::from_millis(1));
3125
3126 assert_eq!(
3127 store_locked.get_latest_component_version_at(&MipComponent::Execution, before),
3128 0
3129 );
3130 assert_eq!(
3131 store_active.get_latest_component_version_at(&MipComponent::Execution, before),
3132 0
3133 );
3134 assert_eq!(
3135 store_locked.get_latest_component_version_at(&MipComponent::Execution, after),
3136 2
3137 );
3138 assert_eq!(
3139 store_active.get_latest_component_version_at(&MipComponent::Execution, after),
3140 2
3141 );
3142 }
3143}