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 {
576 let lock = self.0.read();
577 let store = lock.deref();
578 store
580 .store
581 .iter()
582 .rev()
583 .find_map(|(k, v)| (matches!(v.state, ComponentState::Active(_))).then_some(k.version))
584 .unwrap_or(0)
585 }
586
587 pub fn get_network_version_active_at(&self, ts: MassaTime) -> u32 {
589 let lock = self.0.read();
590 let store = lock.deref();
591 store
592 .store
593 .iter()
594 .rev()
595 .find_map(|(k, v)| match v.state {
596 ComponentState::Active(Active { at }) if at <= ts => Some(k.version),
597 _ => None,
598 })
599 .unwrap_or(0)
600 }
601
602 pub fn get_network_version_to_announce(&self) -> Option<u32> {
605 let lock = self.0.read();
606 let store = lock.deref();
607 store.store.iter().rev().find_map(|(k, v)| {
611 matches!(
612 &v.state,
613 &ComponentState::Started(_) | &ComponentState::LockedIn(_)
614 )
615 .then_some(k.version)
616 })
617 }
618
619 pub fn update_network_version_stats(
620 &mut self,
621 slot_timestamp: MassaTime,
622 network_versions: Option<(u32, Option<u32>)>,
623 ) {
624 let mut lock = self.0.write();
625 lock.update_network_version_stats(slot_timestamp, network_versions);
626 }
627
628 #[allow(clippy::result_large_err)]
629 pub fn update_with(
630 &mut self,
631 mip_store: &MipStore,
632 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), UpdateWithError> {
633 let mut lock = self.0.write();
634 let lock_other = mip_store.0.read();
635 lock.update_with(lock_other.deref())
636 }
637
638 pub fn get_latest_component_version_at(&self, component: &MipComponent, ts: MassaTime) -> u32 {
642 let guard = self.0.read();
643 guard.get_latest_component_version_at(component, ts)
644 }
645
646 pub(crate) fn get_all_active_component_versions(&self, component: &MipComponent) -> Vec<u32> {
648 let guard = self.0.read();
649 guard.get_all_active_component_versions(component)
650 }
651
652 pub(crate) fn get_all_component_versions(
654 &self,
655 component: &MipComponent,
656 ) -> BTreeMap<u32, ComponentStateTypeId> {
657 let guard = self.0.read();
658 guard.get_all_component_versions(component)
659 }
660
661 pub fn get_mip_status(&self) -> BTreeMap<MipInfo, ComponentStateTypeId> {
665 let guard = self.0.read();
666 guard
667 .store
668 .iter()
669 .map(|(mip_info, mip_state)| {
670 (
671 mip_info.clone(),
672 ComponentStateTypeId::from(&mip_state.state),
673 )
674 })
675 .collect()
676 }
677
678 pub fn is_consistent_with_shutdown_period(
680 &self,
681 shutdown_start: Slot,
682 shutdown_end: Slot,
683 thread_count: u8,
684 t0: MassaTime,
685 genesis_timestamp: MassaTime,
686 ) -> Result<(), IsConsistentWithShutdownPeriodError> {
687 let guard = self.0.read();
688 guard.is_consistent_with_shutdown_period(
689 shutdown_start,
690 shutdown_end,
691 thread_count,
692 t0,
693 genesis_timestamp,
694 )
695 }
696
697 #[allow(dead_code)]
698 pub fn update_for_network_shutdown(
699 &mut self,
700 shutdown_start: Slot,
701 shutdown_end: Slot,
702 thread_count: u8,
703 t0: MassaTime,
704 genesis_timestamp: MassaTime,
705 ) -> Result<(), ModelsError> {
706 let mut guard = self.0.write();
707 guard.update_for_network_shutdown(
708 shutdown_start,
709 shutdown_end,
710 thread_count,
711 t0,
712 genesis_timestamp,
713 )
714 }
715
716 pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool {
717 let guard = self.0.read();
718 guard.is_key_value_valid(serialized_key, serialized_value)
719 }
720
721 pub fn update_batches(
724 &self,
725 db_batch: &mut DBBatch,
726 db_versioning_batch: &mut DBBatch,
727 between: Option<(&MassaTime, &MassaTime)>,
728 ) -> Result<(), SerializeError> {
729 let guard = self.0.read();
730 guard.update_batches(db_batch, db_versioning_batch, between)
731 }
732
733 pub fn extend_from_db(
734 &mut self,
735 db: ShareableMassaDBController,
736 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), ExtendFromDbError> {
737 let mut guard = self.0.write();
738 guard.extend_from_db(db)
739 }
740
741 pub fn started_mips_missing_from_db(
751 &self,
752 db: &ShareableMassaDBController,
753 at: MassaTime,
754 ) -> Vec<MipInfo> {
755 let guard = self.0.read();
756 guard.started_mips_missing_from_db(db, at)
757 }
758
759 pub fn reset_db(&self, db: ShareableMassaDBController) {
760 {
761 let mut guard = db.write();
762 guard.delete_prefix(MIP_STORE_PREFIX, STATE_CF, None);
763 guard.delete_prefix(MIP_STORE_PREFIX, VERSIONING_CF, None);
764 guard.delete_prefix(MIP_STORE_STATS_PREFIX, VERSIONING_CF, None);
765 }
766 }
767
768 pub fn try_from_db(
770 db: ShareableMassaDBController,
771 cfg: MipStatsConfig,
772 ) -> Result<Self, ExtendFromDbError> {
773 MipStoreRaw::try_from_db(db, cfg).map(|store_raw| Self(Arc::new(RwLock::new(store_raw))))
774 }
775
776 }
782
783impl<const N: usize> TryFrom<([(MipInfo, MipState); N], MipStatsConfig)> for MipStore {
784 type Error = UpdateWithError;
785
786 fn try_from(
787 (value, cfg): ([(MipInfo, MipState); N], MipStatsConfig),
788 ) -> Result<Self, Self::Error> {
789 MipStoreRaw::try_from((value, cfg)).map(|store_raw| Self(Arc::new(RwLock::new(store_raw))))
790 }
791}
792
793#[derive(Debug, Clone, PartialEq)]
795pub struct MipStatsConfig {
796 pub block_count_considered: usize,
797 pub warn_announced_version_ratio: Ratio<u64>,
798}
799
800#[derive(Debug, Clone, PartialEq)]
802pub(crate) struct MipStoreStats {
803 pub(crate) config: MipStatsConfig,
805 pub(crate) latest_announcements: VecDeque<u32>,
808 pub(crate) network_version_counters: HashMap<u32, u64>,
812}
813
814impl MipStoreStats {
815 pub(crate) fn new(config: MipStatsConfig) -> Self {
816 Self {
817 config: config.clone(),
818 latest_announcements: VecDeque::with_capacity(config.block_count_considered),
819 network_version_counters: HashMap::with_capacity(config.block_count_considered),
820 }
821 }
822
823 fn reset(&mut self) {
825 self.latest_announcements.clear();
826 self.network_version_counters.clear();
827 }
828}
829
830#[derive(Error, Debug, PartialEq)]
832pub enum UpdateWithError {
833 #[error("MipInfo {0:#?} is not consistent with state: {1:#?}, error: {2}")]
835 NonConsistent(MipInfo, MipState, IsConsistentError),
836 #[error("For MipInfo {0:?}, trying to downgrade from state {1:?} to {2:?}")]
838 Downgrade(MipInfo, ComponentState, ComponentState),
839 #[error("MipInfo {0:?} has overlapping data of MipInfo {1:?}")]
841 Overlapping(MipInfo, MipInfo),
842 #[error("MipInfo {0:?} has an invalid activation delay value: {1}, min allowed: {2}")]
843 InvalidActivationDelay(MipInfo, MassaTime, MassaTime),
844}
845
846#[derive(Error, Debug)]
848pub enum ExtendFromDbError {
849 #[error("Unable to get an handle over db column: {0}")]
850 UnknownDbColumn(String),
851 #[error("{0}")]
852 Update(#[from] UpdateWithError),
853 #[error("{0}")]
854 Deserialize(String),
855}
856
857#[derive(Error, Debug)]
859pub enum IsConsistentWithShutdownPeriodError {
860 #[error("{0}")]
861 Update(#[from] ModelsError),
862 #[error("MipInfo: {0:?} (state: {1:?}) is not consistent with shutdown: {2} {3}")]
863 NonConsistent(MipInfo, ComponentState, MassaTime, MassaTime),
864}
865
866#[derive(Error, Debug)]
867pub enum IsKVValidError {
868 #[error("{0}")]
869 Deserialize(String),
870 #[error("Invalid prefix for key")]
871 InvalidPrefix,
872}
873
874#[derive(Debug, Clone, PartialEq)]
876pub struct MipStoreRaw {
877 pub(crate) store: BTreeMap<MipInfo, MipState>,
878 pub(crate) stats: MipStoreStats,
879}
880
881impl MipStoreRaw {
882 #[allow(clippy::result_large_err)]
885 pub fn update_with(
886 &mut self,
887 store_raw: &MipStoreRaw,
888 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), UpdateWithError> {
889 let mut component_versions: HashMap<MipComponent, u32> = self
898 .store
899 .iter()
900 .flat_map(|c| {
901 c.0.components
902 .iter()
903 .map(|(mip_component, component_version)| {
904 (mip_component.clone(), *component_version)
905 })
906 })
907 .collect();
908 let mut names: HashSet<String> = self.store.keys().map(|mi| mi.name.clone()).collect();
909 let mut to_update: BTreeMap<MipInfo, MipState> = Default::default();
910 let mut to_add: BTreeMap<MipInfo, MipState> = Default::default();
911 let mut has_error: Option<UpdateWithError> = None;
912
913 for (m_info, m_state) in store_raw.store.iter() {
914 if let Err(e) = m_state.is_consistent_with(m_info) {
915 has_error = Some(UpdateWithError::NonConsistent(
917 m_info.clone(),
918 m_state.clone(),
919 e,
920 ));
921 break;
922 }
923
924 if let Some(m_state_orig) = self.store.get(m_info) {
925 let m_state_id: u32 = ComponentStateTypeId::from(&m_state.state).into();
928 let m_state_orig_id: u32 = ComponentStateTypeId::from(&m_state_orig.state).into();
929
930 if matches!(
932 m_state_orig.state,
933 ComponentState::Defined(_)
934 | ComponentState::Started(_)
935 | ComponentState::LockedIn(_)
936 ) {
937 if m_state_id >= m_state_orig_id {
940 to_update.insert(m_info.clone(), m_state.clone());
941 } else {
942 has_error = Some(UpdateWithError::Downgrade(
944 m_info.clone(),
945 m_state_orig.state,
946 m_state.state,
947 ));
948 break;
949 }
950 }
951 } else {
952 let last_m_info_ = to_add
956 .last_key_value()
957 .map(|(mi, _)| mi)
958 .or(self.store.last_key_value().map(|(mi, _)| mi));
959
960 if let Some(last_m_info) = last_m_info_ {
961 let mut component_version_compatible = true;
963 for (component, component_version) in m_info.components.iter() {
964 if component_version <= component_versions.get(component).unwrap_or(&0) {
965 component_version_compatible = false;
966 break;
967 }
968 }
969
970 #[cfg(not(any(test, feature = "test-exports")))]
971 if m_info.activation_delay < VERSIONING_ACTIVATION_DELAY_MIN {
972 has_error = Some(UpdateWithError::InvalidActivationDelay(
973 m_info.clone(),
974 m_info.activation_delay,
975 VERSIONING_ACTIVATION_DELAY_MIN,
976 ));
977 break;
978 }
979
980 if m_info.start > last_m_info.timeout
981 && m_info.timeout > m_info.start
982 && m_info.version > last_m_info.version
983 && !names.contains(&m_info.name)
984 && component_version_compatible
985 {
986 to_add.insert(m_info.clone(), m_state.clone());
988 names.insert(m_info.name.clone());
989 for (component, component_version) in m_info.components.iter() {
990 component_versions.insert(component.clone(), *component_version);
991 }
992 } else {
993 has_error = Some(UpdateWithError::Overlapping(
996 m_info.clone(),
997 last_m_info.clone(),
998 ));
999 break;
1000 }
1001 } else {
1002 to_add.insert(m_info.clone(), m_state.clone());
1004 names.insert(m_info.name.clone());
1005 }
1006 }
1007 }
1008
1009 match has_error {
1010 None => {
1011 let updated: Vec<MipInfo> = to_update.keys().cloned().collect();
1012
1013 self.store.append(&mut to_update);
1017 Ok((updated, to_add))
1018 }
1019 Some(e) => Err(e),
1020 }
1021 }
1022
1023 pub fn update_network_version_stats(
1024 &mut self,
1025 slot_timestamp: MassaTime,
1026 network_versions: Option<(u32, Option<u32>)>,
1027 ) {
1028 if let Some((_current_network_version, announced_network_version_)) = network_versions {
1029 let announced_network_version = announced_network_version_.unwrap_or(0);
1030
1031 let removed_version_ = match self.stats.latest_announcements.len() {
1032 n if n >= self.stats.config.block_count_considered => {
1033 self.stats.latest_announcements.pop_front()
1034 }
1035 _ => None,
1036 };
1037 self.stats
1038 .latest_announcements
1039 .push_back(announced_network_version);
1040
1041 let mut network_version_count = *self
1043 .stats
1044 .network_version_counters
1045 .entry(announced_network_version)
1046 .and_modify(|v| *v = v.saturating_add(1))
1047 .or_insert(1);
1048
1049 if let Some(removed_version) = removed_version_ {
1053 if let Entry::Occupied(mut e) =
1054 self.stats.network_version_counters.entry(removed_version)
1055 {
1056 let entry_value = e.get_mut();
1057 *entry_value = entry_value.saturating_sub(1);
1058 network_version_count = *entry_value;
1059 if *entry_value == 0 {
1060 self.stats.network_version_counters.remove(&removed_version);
1061 }
1062 }
1063 }
1064
1065 if announced_network_version != 0 {
1066 let vote_ratio = Ratio::new(
1067 network_version_count,
1068 self.stats.config.block_count_considered as u64,
1069 );
1070
1071 if vote_ratio > self.stats.config.warn_announced_version_ratio {
1072 let last_key_value = self.store.last_key_value();
1073 if let Some((mi, _ms)) = last_key_value {
1074 if announced_network_version > mi.version {
1075 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.",
1080 network_version_count,
1081 self.stats.config.block_count_considered,
1082 announced_network_version
1083 );
1084 }
1085 }
1086 }
1087 }
1088 }
1089
1090 debug!(
1091 "[VERSIONING STATS] stats have {} counters and {} announcements",
1092 self.stats.network_version_counters.len(),
1093 self.stats.latest_announcements.len()
1094 );
1095
1096 self.advance_states_on_updated_stats(slot_timestamp);
1098 }
1099
1100 fn advance_states_on_updated_stats(&mut self, slot_timestamp: MassaTime) {
1102 for (mi, state) in self.store.iter_mut() {
1103 if state.is_final() {
1104 continue;
1106 }
1107
1108 let network_version_count = *self
1109 .stats
1110 .network_version_counters
1111 .get(&mi.version)
1112 .unwrap_or(&0);
1113
1114 let vote_ratio = Ratio::new(
1115 network_version_count,
1116 self.stats.config.block_count_considered as u64,
1117 );
1118
1119 debug!("[VERSIONING STATS] Vote counts / blocks considered = {} / {} (for MipInfo with network version {} - {})",
1120 network_version_count,
1121 self.stats.config.block_count_considered,
1122 mi.version,
1123 mi.name);
1124
1125 let advance_msg = Advance {
1126 start_timestamp: mi.start,
1127 timeout: mi.timeout,
1128 threshold: vote_ratio,
1129 now: slot_timestamp,
1130 activation_delay: mi.activation_delay,
1131 };
1132
1133 state.on_advance(&advance_msg.clone());
1134 }
1135 }
1136
1137 fn get_latest_component_version_at(&self, component: &MipComponent, ts: MassaTime) -> u32 {
1141 let version = self
1142 .store
1143 .iter()
1144 .rev()
1145 .filter(|(mi, ms)| {
1146 mi.components.contains_key(component)
1147 && matches!(ms.state, ComponentState::Active(_))
1148 })
1149 .find_map(|(mi, ms)| {
1150 let res = ms.state_at(ts, mi.start, mi.timeout, mi.activation_delay);
1151 match res {
1152 Ok(ComponentStateTypeId::Active) => mi.components.get(component).copied(),
1153 _ => None,
1154 }
1155 })
1156 .unwrap_or(0);
1157
1158 version
1159 }
1160
1161 fn get_all_active_component_versions(&self, component: &MipComponent) -> Vec<u32> {
1163 let versions_iter = self.store.iter().filter_map(|(mi, ms)| {
1164 if matches!(ms.state, ComponentState::Active(_)) {
1165 mi.components.get(component).copied()
1166 } else {
1167 None
1168 }
1169 });
1170 let versions: Vec<u32> = iter::once(0).chain(versions_iter).collect();
1171 versions
1172 }
1173
1174 fn get_all_component_versions(
1176 &self,
1177 component: &MipComponent,
1178 ) -> BTreeMap<u32, ComponentStateTypeId> {
1179 let versions_iter = self.store.iter().filter_map(|(mi, ms)| {
1180 mi.components
1181 .get(component)
1182 .copied()
1183 .map(|component_version| (component_version, ComponentStateTypeId::from(&ms.state)))
1184 });
1185 iter::once((0, ComponentStateTypeId::Active))
1186 .chain(versions_iter)
1187 .collect()
1188 }
1189
1190 fn is_consistent_with_shutdown_period(
1197 &self,
1198 shutdown_start: Slot,
1199 shutdown_end: Slot,
1200 thread_count: u8,
1201 t0: MassaTime,
1202 genesis_timestamp: MassaTime,
1203 ) -> Result<(), IsConsistentWithShutdownPeriodError> {
1204 let mut has_error: Result<(), IsConsistentWithShutdownPeriodError> = Ok(());
1206
1207 let shutdown_start_ts =
1208 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_start)?;
1209 let shutdown_end_ts =
1210 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_end)?;
1211 let shutdown_range = shutdown_start_ts..=shutdown_end_ts;
1212
1213 for (mip_info, mip_state) in &self.store {
1214 match mip_state.state {
1215 ComponentState::Defined(..)
1216 | ComponentState::Started(..)
1217 | ComponentState::LockedIn(..) => {
1218 if shutdown_range.contains(&mip_info.start)
1220 || shutdown_range.contains(&mip_info.timeout)
1221 {
1222 has_error = Err(IsConsistentWithShutdownPeriodError::NonConsistent(
1224 mip_info.clone(),
1225 mip_state.state,
1226 shutdown_start_ts,
1227 shutdown_end_ts,
1228 ));
1229 break;
1230 }
1231 }
1232 _ => {
1233 }
1236 }
1237 }
1238
1239 has_error
1240 }
1241
1242 fn update_for_network_shutdown(
1243 &mut self,
1244 shutdown_start: Slot,
1245 shutdown_end: Slot,
1246 thread_count: u8,
1247 t0: MassaTime,
1248 genesis_timestamp: MassaTime,
1249 ) -> Result<(), ModelsError> {
1250 let shutdown_start_ts =
1251 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_start)?;
1252 let shutdown_end_ts =
1253 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, shutdown_end)?;
1254 let shutdown_range = shutdown_start_ts..=shutdown_end_ts;
1255
1256 let mut new_store: BTreeMap<MipInfo, MipState> = Default::default();
1257 let mut new_stats = self.stats.clone();
1258 new_stats.reset();
1259
1260 let next_valid_start_ = shutdown_end.get_next_slot(thread_count)?;
1261 let next_valid_start =
1262 get_block_slot_timestamp(thread_count, t0, genesis_timestamp, next_valid_start_)?;
1263
1264 let mut offset: Option<MassaTime> = None;
1265
1266 for (mip_info, mip_state) in &self.store {
1267 match mip_state.state {
1268 ComponentState::Defined(..) => {
1269 let mut new_mip_info = mip_info.clone();
1272
1273 if shutdown_range.contains(&new_mip_info.start) {
1274 let offset_ts = match offset {
1275 Some(offset_ts) => offset_ts,
1276 None => {
1277 let offset_ts = next_valid_start.saturating_sub(mip_info.start);
1278 offset = Some(offset_ts);
1279 offset_ts
1280 }
1281 };
1282
1283 new_mip_info.start = new_mip_info.start.saturating_add(offset_ts);
1284 new_mip_info.timeout = new_mip_info
1285 .start
1286 .saturating_add(mip_info.timeout.saturating_sub(mip_info.start));
1287 }
1288 new_store.insert(new_mip_info, mip_state.clone());
1289 }
1290 ComponentState::Started(..) | ComponentState::LockedIn(..) => {
1291 let mut new_mip_info = mip_info.clone();
1294
1295 let offset_ts = match offset {
1296 Some(offset_ts) => offset_ts,
1297 None => {
1298 let offset_ts = next_valid_start.saturating_sub(mip_info.start);
1299 offset = Some(offset_ts);
1300 offset_ts
1301 }
1302 };
1303
1304 new_mip_info.start = new_mip_info.start.saturating_add(offset_ts);
1305 new_mip_info.timeout = new_mip_info
1306 .start
1307 .saturating_add(mip_info.timeout.saturating_sub(mip_info.start));
1308
1309 let new_mip_state = MipState::reset_from(mip_state)
1311 .ok_or(ModelsError::from("Unable to reset state"))?;
1312 new_store.insert(new_mip_info, new_mip_state.clone());
1314 }
1315 _ => {
1316 new_store.insert(mip_info.clone(), mip_state.clone());
1318 }
1319 }
1320 }
1321
1322 self.store = new_store;
1323 self.stats = new_stats;
1324 Ok(())
1325 }
1326
1327 pub fn get_network_versions_stats(&self) -> HashMap<u32, u64> {
1330 self.stats.network_version_counters.clone()
1331 }
1332
1333 pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool {
1335 self._is_key_value_valid(serialized_key, serialized_value)
1336 .is_ok()
1337 }
1338
1339 pub fn _is_key_value_valid(
1340 &self,
1341 serialized_key: &[u8],
1342 serialized_value: &[u8],
1343 ) -> Result<(), IsKVValidError> {
1344 let mip_info_deser = MipInfoDeserializer::new();
1345 let mip_state_deser = MipStateDeserializer::new();
1346 let mip_store_stats_deser = MipStoreStatsDeserializer::new(
1347 MIP_STORE_STATS_BLOCK_CONSIDERED,
1348 self.stats.config.warn_announced_version_ratio,
1349 );
1350
1351 if serialized_key.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1352 let (rem, _mip_info) = mip_info_deser
1353 .deserialize::<DeserializeError>(&serialized_key[MIP_STORE_PREFIX.len()..])
1354 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1355
1356 if !rem.is_empty() {
1357 return Err(IsKVValidError::Deserialize(
1358 "Rem not empty after deserialization".to_string(),
1359 ));
1360 }
1361
1362 let (rem2, _mip_state) = mip_state_deser
1363 .deserialize::<DeserializeError>(serialized_value)
1364 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1365
1366 if !rem2.is_empty() {
1367 return Err(IsKVValidError::Deserialize(
1368 "Rem not empty after deserialization".to_string(),
1369 ));
1370 }
1371 } else if serialized_key.starts_with(MIP_STORE_STATS_PREFIX.as_bytes()) {
1372 let (rem, _mip_store_stats) = mip_store_stats_deser
1373 .deserialize::<DeserializeError>(serialized_value)
1374 .map_err(|e| IsKVValidError::Deserialize(e.to_string()))?;
1375
1376 if !rem.is_empty() {
1377 return Err(IsKVValidError::Deserialize(
1378 "Rem not empty after deserialization".to_string(),
1379 ));
1380 }
1381 } else {
1382 return Err(IsKVValidError::InvalidPrefix);
1383 }
1384
1385 Ok(())
1386 }
1387
1388 pub fn update_batches(
1392 &self,
1393 batch: &mut DBBatch,
1394 versioning_batch: &mut DBBatch,
1395 between: Option<(&MassaTime, &MassaTime)>,
1396 ) -> Result<(), SerializeError> {
1397 let mip_info_ser = MipInfoSerializer::new();
1398 let mip_state_ser = MipStateSerializer::new();
1399
1400 let bounds = match between {
1401 Some(between) => (*between.0)..(*between.1),
1402 None => MassaTime::from_millis(0)..MassaTime::max(),
1403 };
1404 let mut key = Vec::new();
1405 let mut value = Vec::new();
1406
1407 for (mip_info, mip_state) in self.store.iter() {
1408 if let Some((advance, state_id)) = mip_state.history.last_key_value() {
1409 if bounds.contains(&advance.now) {
1410 key.extend(MIP_STORE_PREFIX.as_bytes().to_vec());
1411 mip_info_ser.serialize(mip_info, &mut key)?;
1412 mip_state_ser.serialize(mip_state, &mut value)?;
1413 match state_id {
1414 ComponentStateTypeId::Active => {
1415 batch.insert(key.clone(), Some(value.clone()));
1416 versioning_batch.insert(key.clone(), None);
1418 }
1419 _ => {
1420 versioning_batch.insert(key.clone(), Some(value.clone()));
1421 }
1422 }
1423 key.clear();
1424 value.clear();
1425 }
1426 }
1427 }
1428
1429 value.clear();
1430 let mip_stats_ser = MipStoreStatsSerializer::new();
1431 mip_stats_ser.serialize(&self.stats, &mut value)?;
1432 versioning_batch.insert(
1433 MIP_STORE_STATS_PREFIX.as_bytes().to_vec(),
1434 Some(value.clone()),
1435 );
1436
1437 Ok(())
1438 }
1439
1440 fn extend_from_db(
1442 &mut self,
1443 db: ShareableMassaDBController,
1444 ) -> Result<(Vec<MipInfo>, BTreeMap<MipInfo, MipState>), ExtendFromDbError> {
1445 let mip_info_deser = MipInfoDeserializer::new();
1446 let mip_state_deser = MipStateDeserializer::new();
1447 let mip_store_stats_deser = MipStoreStatsDeserializer::new(
1448 MIP_STORE_STATS_BLOCK_CONSIDERED,
1449 self.stats.config.warn_announced_version_ratio,
1450 );
1451
1452 let db = db.read();
1453
1454 let mut update_data: BTreeMap<MipInfo, MipState> = Default::default();
1456 for (ser_mip_info, ser_mip_state) in
1457 db.prefix_iterator_cf(STATE_CF, MIP_STORE_PREFIX.as_bytes())
1458 {
1459 if !ser_mip_info.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1460 break;
1461 }
1462
1463 let (_, mip_info) = mip_info_deser
1465 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1466 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1467
1468 let (_, mip_state) = mip_state_deser
1469 .deserialize::<DeserializeError>(&ser_mip_state)
1470 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1471
1472 update_data.insert(mip_info, mip_state);
1473 }
1474
1475 let (mut updated, mut added) = match update_data.is_empty() {
1476 true => (vec![], BTreeMap::new()),
1477 false => {
1478 let store_raw_ = MipStoreRaw {
1479 store: update_data,
1480 stats: MipStoreStats {
1481 config: MipStatsConfig {
1482 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
1483 warn_announced_version_ratio: self
1484 .stats
1485 .config
1486 .warn_announced_version_ratio,
1487 },
1488 latest_announcements: Default::default(),
1489 network_version_counters: Default::default(),
1490 },
1491 };
1492 self.update_with(&store_raw_)?
1494 }
1495 };
1496
1497 let mut update_data: BTreeMap<MipInfo, MipState> = Default::default();
1498
1499 for (ser_mip_info, ser_mip_state) in
1501 db.prefix_iterator_cf(VERSIONING_CF, MIP_STORE_PREFIX.as_bytes())
1502 {
1503 match &ser_mip_info {
1506 key if key.starts_with(MIP_STORE_PREFIX.as_bytes()) => {
1507 let (_, mip_info) = mip_info_deser
1508 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1509 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1510
1511 let (_, mip_state) = mip_state_deser
1512 .deserialize::<DeserializeError>(&ser_mip_state)
1513 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1514
1515 update_data.insert(mip_info, mip_state);
1516 }
1517 key if key.starts_with(MIP_STORE_STATS_PREFIX.as_bytes()) => {
1518 let (_, mip_store_stats) = mip_store_stats_deser
1519 .deserialize::<DeserializeError>(&ser_mip_state)
1520 .map_err(|e| ExtendFromDbError::Deserialize(e.to_string()))?;
1521
1522 self.stats = mip_store_stats;
1523 }
1524 _ => {
1525 break;
1526 }
1527 }
1528 }
1529
1530 if !update_data.is_empty() {
1531 let store_raw_ = MipStoreRaw {
1532 store: update_data,
1533 stats: MipStoreStats {
1534 config: MipStatsConfig {
1535 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
1536 warn_announced_version_ratio: self
1537 .stats
1538 .config
1539 .warn_announced_version_ratio,
1540 },
1541 latest_announcements: Default::default(),
1542 network_version_counters: Default::default(),
1543 },
1544 };
1545 let (updated_2, added_2) = self.update_with(&store_raw_)?;
1547 updated.extend(updated_2);
1548 added.extend(added_2);
1549 }
1550
1551 Ok((updated, added))
1552 }
1553
1554 fn started_mips_missing_from_db(
1556 &self,
1557 db: &ShareableMassaDBController,
1558 at: MassaTime,
1559 ) -> Vec<MipInfo> {
1560 let mip_info_deser = MipInfoDeserializer::new();
1561 let db = db.read();
1562
1563 let mut in_db: HashSet<String> = HashSet::new();
1566 for cf in [STATE_CF, VERSIONING_CF] {
1567 for (ser_mip_info, _) in db.prefix_iterator_cf(cf, MIP_STORE_PREFIX.as_bytes()) {
1568 if !ser_mip_info.starts_with(MIP_STORE_PREFIX.as_bytes()) {
1569 break;
1570 }
1571 if let Ok((_, mip_info)) = mip_info_deser
1572 .deserialize::<DeserializeError>(&ser_mip_info[MIP_STORE_PREFIX.len()..])
1573 {
1574 in_db.insert(mip_info.name);
1575 }
1576 }
1577 }
1578
1579 self.store
1580 .keys()
1581 .filter(|mip_info| mip_info.start <= at && !in_db.contains(&mip_info.name))
1582 .cloned()
1583 .collect()
1584 }
1585
1586 fn try_from_db(
1588 db: ShareableMassaDBController,
1589 cfg: MipStatsConfig,
1590 ) -> Result<Self, ExtendFromDbError> {
1591 let mut store_raw = MipStoreRaw {
1592 store: Default::default(),
1593 stats: MipStoreStats {
1594 config: cfg,
1595 latest_announcements: Default::default(),
1596 network_version_counters: Default::default(),
1597 },
1598 };
1599
1600 let (_updated, mut added) = store_raw.extend_from_db(db)?;
1601 store_raw.store.append(&mut added);
1602 Ok(store_raw)
1603 }
1604}
1605
1606impl<const N: usize> TryFrom<([(MipInfo, MipState); N], MipStatsConfig)> for MipStoreRaw {
1607 type Error = UpdateWithError;
1608
1609 fn try_from(
1610 (value, cfg): ([(MipInfo, MipState); N], MipStatsConfig),
1611 ) -> Result<Self, Self::Error> {
1612 let mut store = Self {
1614 store: Default::default(),
1615 stats: MipStoreStats::new(cfg.clone()),
1616 };
1617
1618 let other_store = Self {
1620 store: BTreeMap::from(value),
1621 stats: MipStoreStats::new(cfg),
1622 };
1623
1624 match store.update_with(&other_store) {
1626 Ok((_updated, mut added)) => {
1627 store.store.append(&mut added);
1628 Ok(store)
1629 }
1630 Err(e) => Err(e),
1631 }
1632 }
1633}
1634
1635#[cfg(test)]
1638mod test {
1639 use super::*;
1640
1641 use assert_matches::assert_matches;
1642 use massa_db_exports::{MassaDBConfig, MassaDBController, MassaIteratorMode};
1643 use massa_db_worker::MassaDB;
1644 use more_asserts::{assert_gt, assert_le};
1645 use parking_lot::RwLock;
1646 use std::ops::{Add, Sub};
1647 use std::sync::Arc;
1648 use tempfile::tempdir;
1649
1650 use crate::test_helpers::versioning_helpers::advance_state_until;
1651
1652 use massa_models::config::{MIP_STORE_STATS_BLOCK_CONSIDERED, T0, THREAD_COUNT};
1653 use massa_models::timeslots::get_closest_slot_to_timestamp;
1654
1655 impl PartialEq<ComponentState> for MipState {
1657 fn eq(&self, other: &ComponentState) -> bool {
1658 self.state == *other
1659 }
1660 }
1661
1662 impl From<(&MipInfo, &Ratio<u64>, &MassaTime)> for Advance {
1664 fn from((mip_info, threshold, now): (&MipInfo, &Ratio<u64>, &MassaTime)) -> Self {
1665 Self {
1666 start_timestamp: mip_info.start,
1667 timeout: mip_info.timeout,
1668 threshold: *threshold,
1669 now: *now,
1670 activation_delay: mip_info.activation_delay,
1671 }
1672 }
1673 }
1674
1675 fn get_a_version_info() -> (MassaTime, MassaTime, MipInfo) {
1676 let start = MassaTime::from_utc_ymd_hms(2017, 11, 1, 7, 33, 44).unwrap();
1681 let timeout = MassaTime::from_utc_ymd_hms(2017, 11, 11, 7, 33, 44).unwrap();
1682
1683 (
1684 start,
1685 timeout,
1686 MipInfo {
1687 name: "MIP-0002".to_string(),
1688 version: 2,
1689 components: BTreeMap::from([(MipComponent::Address, 1)]),
1690 start,
1691 timeout,
1692 activation_delay: MassaTime::from_millis(20),
1693 },
1694 )
1695 }
1696
1697 #[test]
1698 fn test_state_advance_from_defined() {
1699 let (_, _, mi) = get_a_version_info();
1701 let mut state: ComponentState = Default::default();
1702 assert_eq!(state, ComponentState::defined());
1703
1704 let now = mi.start.saturating_sub(MassaTime::from_millis(1));
1705 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1706
1707 state = state.on_advance(advance_msg.clone());
1708 assert_eq!(state, ComponentState::defined());
1709
1710 let now = mi.start.saturating_add(MassaTime::from_millis(5));
1711 advance_msg.now = now;
1712 state = state.on_advance(advance_msg);
1713
1714 assert_eq!(
1716 state,
1717 ComponentState::Started(Started {
1718 vote_ratio: Ratio::zero()
1719 })
1720 );
1721 }
1722
1723 #[test]
1724 fn test_state_advance_from_started() {
1725 let (_, _, mi) = get_a_version_info();
1727 let mut state: ComponentState = ComponentState::started(Default::default());
1728
1729 let now = mi.start;
1730 let threshold_too_low =
1731 VERSIONING_THRESHOLD_TRANSITION_ACCEPTED.sub(Ratio::new_raw(10, 100));
1732 let threshold_ok = VERSIONING_THRESHOLD_TRANSITION_ACCEPTED.add(Ratio::new_raw(1, 100));
1733 assert_le!(threshold_ok, Ratio::from_integer(1));
1734 let mut advance_msg = Advance::from((&mi, &threshold_too_low, &now));
1735
1736 state = state.on_advance(advance_msg.clone());
1737 assert_eq!(state, ComponentState::started(threshold_too_low));
1738 advance_msg.threshold = threshold_ok;
1739 state = state.on_advance(advance_msg);
1740 assert_eq!(state, ComponentState::locked_in(now));
1741 }
1742
1743 #[test]
1744 fn test_state_advance_from_locked_in() {
1745 let (_, _, mi) = get_a_version_info();
1747
1748 let locked_in_at = mi.start.saturating_add(MassaTime::from_millis(1));
1749 let mut state: ComponentState = ComponentState::locked_in(locked_in_at);
1750
1751 let now = mi.start;
1752 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1753
1754 state = state.on_advance(advance_msg.clone());
1755 assert_eq!(state, ComponentState::locked_in(locked_in_at));
1756
1757 advance_msg.now = advance_msg
1758 .timeout
1759 .saturating_add(MassaTime::from_millis(1));
1760 state = state.on_advance(advance_msg);
1761 assert!(matches!(state, ComponentState::Active(_)));
1762 }
1763
1764 #[test]
1765 fn test_state_advance_from_active() {
1766 let (start, _, mi) = get_a_version_info();
1768 let mut state = ComponentState::active(start);
1769 let now = mi.start;
1770 let advance = Advance::from((&mi, &Ratio::zero(), &now));
1771
1772 state = state.on_advance(advance);
1773 assert!(matches!(state, ComponentState::Active(_)));
1774 }
1775
1776 #[test]
1777 fn test_state_advance_from_failed() {
1778 let (_, _, mi) = get_a_version_info();
1780 let mut state = ComponentState::failed();
1781 let now = mi.start;
1782 let advance = Advance::from((&mi, &Ratio::zero(), &now));
1783 state = state.on_advance(advance);
1784 assert_eq!(state, ComponentState::failed());
1785 }
1786
1787 #[test]
1788 fn test_state_advance_to_failed() {
1789 let (_, _, mi) = get_a_version_info();
1791 let now = mi.timeout.saturating_add(MassaTime::from_millis(1));
1792 let advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1793
1794 let mut state: ComponentState = Default::default(); state = state.on_advance(advance_msg.clone());
1796 assert_eq!(state, ComponentState::Failed(Failed {}));
1797
1798 let mut state = ComponentState::started(Default::default());
1799 state = state.on_advance(advance_msg.clone());
1800 assert_eq!(state, ComponentState::Failed(Failed {}));
1801 }
1802
1803 #[test]
1804 fn test_state_with_history() {
1805 let (start, _, mi) = get_a_version_info();
1808 let now_0 = start;
1809 let mut state = MipState::new(now_0);
1810
1811 assert_eq!(state, ComponentState::defined());
1812
1813 let now = mi.start.saturating_add(MassaTime::from_millis(15));
1814 let mut advance_msg = Advance::from((&mi, &Ratio::zero(), &now));
1815
1816 state.on_advance(&advance_msg);
1818 assert_eq!(state, ComponentState::started(Ratio::zero()));
1819
1820 assert_eq!(state.history.len(), 2);
1822 assert!(matches!(
1823 state.history.first_key_value(),
1824 Some((&AdvanceLW { .. }, &ComponentStateTypeId::Defined))
1825 ));
1826 assert!(matches!(
1827 state.history.last_key_value(),
1828 Some((&AdvanceLW { .. }, &ComponentStateTypeId::Started))
1829 ));
1830
1831 let state_id_ = state.state_at(
1835 mi.start.saturating_sub(MassaTime::from_millis(5)),
1836 mi.start,
1837 mi.timeout,
1838 mi.activation_delay,
1839 );
1840 assert!(matches!(
1841 state_id_,
1842 Err(StateAtError::BeforeInitialState(_, _))
1843 ));
1844 let state_id = state
1846 .state_at(mi.start, mi.start, mi.timeout, mi.activation_delay)
1847 .unwrap();
1848 assert_eq!(state_id, ComponentStateTypeId::Defined);
1849 let state_id = state
1851 .state_at(now, mi.start, mi.timeout, mi.activation_delay)
1852 .unwrap();
1853 assert_eq!(state_id, ComponentStateTypeId::Started);
1854
1855 let after_started_ts = now.saturating_add(MassaTime::from_millis(15));
1857 let state_id_ = state.state_at(after_started_ts, mi.start, mi.timeout, mi.activation_delay);
1858 assert_eq!(state_id_, Err(StateAtError::Unpredictable));
1859
1860 let after_timeout_ts = mi.timeout.saturating_add(MassaTime::from_millis(15));
1862 let state_id = state
1863 .state_at(after_timeout_ts, mi.start, mi.timeout, mi.activation_delay)
1864 .unwrap();
1865 assert_eq!(state_id, ComponentStateTypeId::Failed);
1866
1867 let threshold = VERSIONING_THRESHOLD_TRANSITION_ACCEPTED;
1869 advance_msg.threshold = threshold.add(Ratio::from_integer(1));
1870 advance_msg.now = now.saturating_add(MassaTime::from_millis(1));
1871 state.on_advance(&advance_msg);
1872 assert_eq!(state, ComponentState::locked_in(advance_msg.now));
1873
1874 let after_locked_in_ts = now.saturating_add(MassaTime::from_millis(10));
1877 let state_id = state
1878 .state_at(
1879 after_locked_in_ts,
1880 mi.start,
1881 mi.timeout,
1882 mi.activation_delay,
1883 )
1884 .unwrap();
1885 assert_eq!(state_id, ComponentStateTypeId::LockedIn);
1886 let state_id = state
1888 .state_at(after_timeout_ts, mi.start, mi.timeout, mi.activation_delay)
1889 .unwrap();
1890 assert_eq!(state_id, ComponentStateTypeId::Active);
1891 }
1892
1893 #[test]
1894 fn test_versioning_store_announce_current() {
1895 let (start, timeout, mi) = get_a_version_info();
1898
1899 let mut mi_2 = mi.clone();
1900 mi_2.version += 1;
1901 mi_2.start = timeout
1902 .checked_add(MassaTime::from_millis(1000 * 60 * 60 * 24 * 2))
1903 .unwrap(); mi_2.timeout = timeout
1905 .checked_add(MassaTime::from_millis(1000 * 60 * 60 * 24 * 5))
1906 .unwrap(); let vs_1 = MipState {
1910 state: ComponentState::active(start),
1911 history: Default::default(),
1912 };
1913 let vs_2 = MipState {
1914 state: ComponentState::started(Ratio::zero()),
1915 history: Default::default(),
1916 };
1917
1918 let mip_stats_cfg = MipStatsConfig {
1920 block_count_considered: 10,
1921 warn_announced_version_ratio: Ratio::new_raw(30, 100),
1922 };
1923 let vs_raw = MipStoreRaw {
1924 store: BTreeMap::from([(mi.clone(), vs_1), (mi_2.clone(), vs_2)]),
1925 stats: MipStoreStats::new(mip_stats_cfg.clone()),
1926 };
1927 let vs = MipStore(Arc::new(RwLock::new(vs_raw)));
1929
1930 assert_eq!(vs.get_network_version_current(), mi.version);
1931 assert_eq!(vs.get_network_version_to_announce(), Some(mi_2.version));
1932
1933 let vs_raw = MipStoreRaw {
1935 store: Default::default(),
1936 stats: MipStoreStats::new(mip_stats_cfg),
1937 };
1938 let vs = MipStore(Arc::new(RwLock::new(vs_raw)));
1939 assert_eq!(vs.get_network_version_current(), 0);
1940 assert_eq!(vs.get_network_version_to_announce(), None);
1941 }
1942
1943 #[test]
1944 fn test_is_consistent_with() {
1945 let vi_1 = MipInfo {
1953 name: "MIP-0002".to_string(),
1954 version: 2,
1955 components: BTreeMap::from([(MipComponent::Address, 1)]),
1956 start: MassaTime::from_millis(2),
1957 timeout: MassaTime::from_millis(5),
1958 activation_delay: MassaTime::from_millis(2),
1959 };
1960 let vi_2 = MipInfo {
1962 name: "MIP-0002".to_string(),
1963 version: 2,
1964 components: BTreeMap::from([(MipComponent::Address, 1)]),
1965 start: MassaTime::from_millis(7),
1966 timeout: MassaTime::from_millis(10),
1967 activation_delay: MassaTime::from_millis(2),
1968 };
1969
1970 let vsh = MipState {
1971 state: ComponentState::Error,
1972 history: Default::default(),
1973 };
1974 assert_eq!(
1976 vsh.is_consistent_with(&vi_1),
1977 Err(IsConsistentError::AtError)
1978 );
1979
1980 let vsh = MipState {
1981 state: ComponentState::defined(),
1982 history: Default::default(),
1983 };
1984 assert!(vsh.is_consistent_with(&vi_1).is_err());
1986
1987 let mut vsh = MipState::new(MassaTime::from_millis(1));
1988 assert!(vsh.is_consistent_with(&vi_1).is_ok());
1990 let now = MassaTime::from_millis(3);
1995 let adv = Advance::from((&vi_1, &Ratio::zero(), &now));
1996 vsh.on_advance(&adv);
1997 let now = MassaTime::from_millis(4);
1998 let adv = Advance::from((&vi_1, &Ratio::new_raw(14, 100), &now));
1999 vsh.on_advance(&adv);
2000
2001 assert_eq!(vsh.state, ComponentState::started(Ratio::new_raw(14, 100)));
2003 assert!(vsh.is_consistent_with(&vi_1).is_ok());
2004 assert!(vsh.is_consistent_with(&vi_2).is_err());
2006
2007 let now = MassaTime::from_millis(4);
2009 let adv = Advance::from((&vi_1, &VERSIONING_THRESHOLD_TRANSITION_ACCEPTED, &now));
2010 vsh.on_advance(&adv);
2011
2012 assert_eq!(vsh.state, ComponentState::locked_in(now));
2014 assert!(vsh.is_consistent_with(&vi_1).is_ok());
2015
2016 }
2019
2020 #[test]
2021 fn test_update_with() {
2022 let vi_1 = MipInfo {
2025 name: "MIP-0002".to_string(),
2026 version: 2,
2027 components: BTreeMap::from([(MipComponent::Address, 1)]),
2028 start: MassaTime::from_millis(2),
2029 timeout: MassaTime::from_millis(5),
2030 activation_delay: MassaTime::from_millis(2),
2031 };
2032
2033 let _time = MassaTime::now();
2034 let vs_1 = advance_state_until(ComponentState::active(_time), &vi_1);
2035 assert!(matches!(vs_1.state, ComponentState::Active(_)));
2036
2037 let vi_2 = MipInfo {
2038 name: "MIP-0003".to_string(),
2039 version: 3,
2040 components: BTreeMap::from([(MipComponent::Address, 2)]),
2041 start: MassaTime::from_millis(17),
2042 timeout: MassaTime::from_millis(27),
2043 activation_delay: MassaTime::from_millis(2),
2044 };
2045 let vs_2 = advance_state_until(ComponentState::defined(), &vi_2);
2046
2047 let mip_stats_cfg = MipStatsConfig {
2048 block_count_considered: 10,
2049 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2050 };
2051 let mut vs_raw_1 = MipStoreRaw::try_from((
2052 [(vi_1.clone(), vs_1.clone()), (vi_2.clone(), vs_2.clone())],
2053 mip_stats_cfg.clone(),
2054 ))
2055 .unwrap();
2056
2057 let vs_2_2 = advance_state_until(ComponentState::active(_time), &vi_2);
2058 assert!(matches!(vs_2_2.state, ComponentState::Active(_)));
2059
2060 let vs_raw_2 = MipStoreRaw::try_from((
2061 [(vi_1.clone(), vs_1.clone()), (vi_2.clone(), vs_2_2.clone())],
2062 mip_stats_cfg,
2063 ))
2064 .unwrap();
2065
2066 let (updated, added) = vs_raw_1.update_with(&vs_raw_2).unwrap();
2067
2068 assert!(added.is_empty());
2070 assert_eq!(updated, vec![vi_2.clone()]);
2071
2072 assert_eq!(vs_raw_1.store.get(&vi_1).unwrap().state, vs_1.state);
2074 assert_eq!(vs_raw_1.store.get(&vi_2).unwrap().state, vs_2_2.state);
2075 }
2076
2077 #[test]
2078 fn test_update_with_invalid() {
2079 let mi_1 = MipInfo {
2083 name: "MIP-0001".to_string(),
2084 version: 2,
2085 components: BTreeMap::from([(MipComponent::Address, 1)]),
2086 start: MassaTime::from_millis(0),
2087 timeout: MassaTime::from_millis(5),
2088 activation_delay: MassaTime::from_millis(2),
2089 };
2090 let _time = MassaTime::now();
2091 let ms_1 = advance_state_until(ComponentState::active(_time), &mi_1);
2092 assert!(matches!(ms_1.state, ComponentState::Active(_)));
2093
2094 let mi_2 = MipInfo {
2095 name: "MIP-0002".to_string(),
2096 version: 3,
2097 components: BTreeMap::from([(MipComponent::Address, 2)]),
2098 start: MassaTime::from_millis(17),
2099 timeout: MassaTime::from_millis(27),
2100 activation_delay: MassaTime::from_millis(2),
2101 };
2102 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2103 assert_eq!(ms_2, ComponentState::defined());
2104
2105 let mip_stats_cfg = MipStatsConfig {
2106 block_count_considered: 10,
2107 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2108 };
2109
2110 {
2112 let mut store_1 = MipStoreRaw::try_from((
2113 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2114 mip_stats_cfg.clone(),
2115 ))
2116 .unwrap();
2117
2118 let mut mi_2_2 = mi_2.clone();
2119 mi_2_2.start = mi_1.timeout;
2121 let ms_2_2 = advance_state_until(ComponentState::defined(), &mi_2_2);
2122 let store_2 = MipStoreRaw {
2123 store: BTreeMap::from([
2124 (mi_1.clone(), ms_1.clone()),
2125 (mi_2_2.clone(), ms_2_2.clone()),
2126 ]),
2127 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2128 };
2129
2130 assert_matches!(
2131 store_1.update_with(&store_2),
2132 Err(UpdateWithError::Overlapping(..))
2133 );
2134 assert_eq!(store_1.store.get(&mi_1).unwrap().state, ms_1.state);
2135 assert_eq!(store_1.store.get(&mi_2).unwrap().state, ms_2.state);
2136
2137 {
2139 let _store_2_ = MipStoreRaw::try_from((
2140 [
2141 (mi_1.clone(), ms_1.clone()),
2142 (mi_2_2.clone(), ms_2_2.clone()),
2143 ],
2144 mip_stats_cfg.clone(),
2145 ));
2146 assert!(_store_2_.is_err());
2147 }
2148 }
2149
2150 {
2152 let mut store_1 = MipStoreRaw::try_from((
2153 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2154 mip_stats_cfg.clone(),
2155 ))
2156 .unwrap();
2157
2158 let mut mi_2_2 = mi_2.clone();
2159 mi_2_2.components = mi_1.components.clone();
2161
2162 let ms_2_2 = advance_state_until(ComponentState::defined(), &mi_2_2);
2163 let store_2 = MipStoreRaw {
2164 store: BTreeMap::from([
2165 (mi_1.clone(), ms_1.clone()),
2166 (mi_2_2.clone(), ms_2_2.clone()),
2167 ]),
2168 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2169 };
2170
2171 assert_matches!(
2173 store_1.update_with(&store_2),
2174 Err(UpdateWithError::Overlapping(..))
2175 );
2176 }
2177
2178 {
2180 let mut store_1 =
2181 MipStoreRaw::try_from(([(mi_1.clone(), ms_1.clone())], mip_stats_cfg.clone()))
2182 .unwrap();
2183 let mut mi_2_2 = mi_2.clone();
2184 mi_2_2.version = mi_1.version - 1;
2186
2187 let store_2 = MipStoreRaw {
2188 store: BTreeMap::from([(mi_2_2.clone(), ms_2.clone())]),
2189 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2190 };
2191
2192 assert_matches!(
2193 store_1.update_with(&store_2),
2194 Err(UpdateWithError::Overlapping(..))
2195 );
2196
2197 let mut mi_2_3 = mi_2.clone();
2200 mi_2_3.version = mi_1.version;
2202
2203 let store_2 = MipStoreRaw {
2204 store: BTreeMap::from([(mi_2_3.clone(), ms_2.clone())]),
2205 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2206 };
2207
2208 assert_matches!(
2209 store_1.update_with(&store_2),
2210 Err(UpdateWithError::Overlapping(..))
2211 );
2212 }
2213
2214 {
2216 let mut store_1 =
2217 MipStoreRaw::try_from(([(mi_1.clone(), ms_1.clone())], mip_stats_cfg.clone()))
2218 .unwrap();
2219 let mut mi_2_2 = mi_2.clone();
2220 mi_2_2.name = mi_1.name.clone();
2222
2223 let store_2 = MipStoreRaw {
2224 store: BTreeMap::from([(mi_2_2.clone(), ms_2.clone())]),
2225 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2226 };
2227
2228 assert_matches!(
2229 store_1.update_with(&store_2),
2230 Err(UpdateWithError::Overlapping(..))
2231 );
2232 }
2233
2234 {
2236 let ms_1_1 =
2237 advance_state_until(ComponentState::locked_in(MassaTime::from_millis(0)), &mi_1);
2238 let ms_1_2 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2239 let mut store_1 =
2240 MipStoreRaw::try_from(([(mi_1.clone(), ms_1_1.clone())], mip_stats_cfg.clone()))
2241 .unwrap();
2242
2243 let store_2 = MipStoreRaw {
2245 store: BTreeMap::from([(mi_1.clone(), ms_1_2.clone())]),
2246 stats: MipStoreStats::new(mip_stats_cfg.clone()),
2247 };
2248
2249 assert_matches!(
2250 store_1.update_with(&store_2),
2251 Err(UpdateWithError::Downgrade(..))
2252 );
2253 }
2254 }
2255
2256 #[test]
2257 fn test_try_from_invalid() {
2258 let mip_stats_cfg = MipStatsConfig {
2262 block_count_considered: 10,
2263 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2264 };
2265 let mi_1 = MipInfo {
2266 name: "MIP-0002".to_string(),
2267 version: 2,
2268 components: BTreeMap::from([(MipComponent::Address, 1)]),
2269 start: MassaTime::from_millis(0),
2270 timeout: MassaTime::from_millis(5),
2271 activation_delay: MassaTime::from_millis(2),
2272 };
2273 let _time = MassaTime::now();
2274 let ms_1 = advance_state_until(ComponentState::active(_time), &mi_1);
2275 assert!(matches!(ms_1.state, ComponentState::Active(_)));
2276 {
2277 let mut mi_1_1 = mi_1.clone();
2279 mi_1_1.start = MassaTime::from_millis(5);
2280 mi_1_1.timeout = MassaTime::from_millis(2);
2281
2282 let mip_store =
2283 MipStoreRaw::try_from(([(mi_1_1, ms_1.clone())], mip_stats_cfg.clone()));
2284 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2285 }
2286 {
2287 let ms_1_2 = MipState::new(MassaTime::from_millis(15));
2288 let mut mi_1_2 = mi_1.clone();
2290 mi_1_2.start = MassaTime::from_millis(2);
2291 mi_1_2.timeout = MassaTime::from_millis(5);
2292
2293 let mip_store = MipStoreRaw::try_from(([(mi_1_2, ms_1_2)], mip_stats_cfg.clone()));
2294 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2295 }
2296 {
2297 let ms_1_2 = MipState::new(MassaTime::from_millis(15));
2298 let mut mi_1_2 = mi_1.clone();
2300 mi_1_2.start = MassaTime::from_millis(16);
2301 mi_1_2.timeout = MassaTime::from_millis(5);
2302
2303 let mip_store = MipStoreRaw::try_from(([(mi_1_2, ms_1_2)], mip_stats_cfg));
2304 assert_matches!(mip_store, Err(UpdateWithError::NonConsistent(..)));
2305 }
2306 }
2307
2308 #[test]
2309 fn test_empty_mip_store() {
2310 let mip_stats_config = MipStatsConfig {
2313 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2314 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2315 };
2316
2317 let mip_store = MipStore::try_from(([], mip_stats_config));
2318 assert!(mip_store.is_ok());
2319 }
2320
2321 #[test]
2322 fn test_update_with_unknown() {
2323 let mip_stats_config = MipStatsConfig {
2327 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2328 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2329 };
2330
2331 let mut mip_store_raw_1 = MipStoreRaw::try_from(([], mip_stats_config.clone())).unwrap();
2332
2333 let mi_1 = MipInfo {
2334 name: "MIP-0002".to_string(),
2335 version: 2,
2336 components: BTreeMap::from([(MipComponent::__Nonexhaustive, 1)]),
2337 start: MassaTime::from_millis(0),
2338 timeout: MassaTime::from_millis(5),
2339 activation_delay: MassaTime::from_millis(2),
2340 };
2341 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2342 assert_eq!(ms_1, ComponentState::defined());
2343 let mip_store_raw_2 = MipStoreRaw {
2344 store: BTreeMap::from([(mi_1.clone(), ms_1.clone())]),
2345 stats: MipStoreStats::new(mip_stats_config.clone()),
2346 };
2347
2348 let (updated, added) = mip_store_raw_1.update_with(&mip_store_raw_2).unwrap();
2349
2350 assert_eq!(updated.len(), 0);
2351 assert_eq!(added.len(), 1);
2352 assert_eq!(added.get(&mi_1).unwrap().state, ComponentState::defined());
2353 }
2354
2355 #[test]
2356 fn test_mip_store_network_restart() {
2357 let genesis_timestamp = MassaTime::from_millis(0);
2360
2361 let get_slot_ts =
2363 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2364 let is_consistent = |store: &MipStoreRaw, shutdown_start, shutdown_end| {
2365 store.is_consistent_with_shutdown_period(
2366 shutdown_start,
2367 shutdown_end,
2368 THREAD_COUNT,
2369 T0,
2370 genesis_timestamp,
2371 )
2372 };
2373 let update_store = |store: &mut MipStoreRaw, shutdown_start, shutdown_end| {
2374 store
2375 .update_for_network_shutdown(
2376 shutdown_start,
2377 shutdown_end,
2378 THREAD_COUNT,
2379 T0,
2380 genesis_timestamp,
2381 )
2382 .unwrap()
2383 };
2384 let _dump_store = |store: &MipStoreRaw| {
2385 println!("Dump store:");
2386 for (mip_info, mip_state) in store.store.iter() {
2387 println!(
2388 "mip_info {} {} - start: {} - timeout: {}: state: {:?}",
2389 mip_info.name,
2390 mip_info.version,
2391 get_closest_slot_to_timestamp(
2392 THREAD_COUNT,
2393 T0,
2394 genesis_timestamp,
2395 mip_info.start
2396 ),
2397 get_closest_slot_to_timestamp(
2398 THREAD_COUNT,
2399 T0,
2400 genesis_timestamp,
2401 mip_info.timeout
2402 ),
2403 mip_state.state
2404 );
2405 }
2406 };
2407 let shutdown_start = Slot::new(2, 0);
2410 let shutdown_end = Slot::new(8, 0);
2411
2412 let mip_stats_cfg = MipStatsConfig {
2413 block_count_considered: 10,
2414 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2415 };
2416 let mut mi_1 = MipInfo {
2417 name: "MIP-0002".to_string(),
2418 version: 2,
2419 components: BTreeMap::from([(MipComponent::Address, 1)]),
2420 start: MassaTime::from_millis(2),
2421 timeout: MassaTime::from_millis(5),
2422 activation_delay: MassaTime::from_millis(100),
2423 };
2424 let mut mi_2 = MipInfo {
2425 name: "MIP-0003".to_string(),
2426 version: 3,
2427 components: BTreeMap::from([(MipComponent::Address, 2)]),
2428 start: MassaTime::from_millis(7),
2429 timeout: MassaTime::from_millis(11),
2430 activation_delay: MassaTime::from_millis(100),
2431 };
2432
2433 {
2435 mi_1.start = get_slot_ts(Slot::new(3, 7));
2436 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2437 mi_2.start = get_slot_ts(Slot::new(7, 7));
2438 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2439
2440 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2441 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2442 assert_eq!(ms_1, ComponentState::defined());
2443 assert_eq!(ms_2, ComponentState::defined());
2444
2445 let mut store = MipStoreRaw::try_from((
2446 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2447 mip_stats_cfg.clone(),
2448 ))
2449 .unwrap();
2450
2451 match is_consistent(&store, shutdown_start, shutdown_end) {
2452 Err(IsConsistentWithShutdownPeriodError::NonConsistent(mi, ..)) => {
2453 assert_eq!(mi, mi_1);
2454 }
2455 _ => panic!("is_consistent expects a non consistent error"),
2456 }
2457
2458 update_store(&mut store, shutdown_start, shutdown_end);
2459 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2460 }
2462
2463 {
2465 mi_1.start = get_slot_ts(Slot::new(9, 7));
2466 mi_1.timeout = get_slot_ts(Slot::new(11, 7));
2467 mi_2.start = get_slot_ts(Slot::new(12, 7));
2468 mi_2.timeout = get_slot_ts(Slot::new(19, 7));
2469
2470 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2471 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2472 assert_eq!(ms_1, ComponentState::defined());
2473 assert_eq!(ms_2, ComponentState::defined());
2474 let mut store = MipStoreRaw::try_from((
2475 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2476 mip_stats_cfg.clone(),
2477 ))
2478 .unwrap();
2479 let store_orig = store.clone();
2480
2481 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2483 update_store(&mut store, shutdown_start, shutdown_end);
2485 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2486 assert_eq!(store_orig, store);
2490 }
2491
2492 {
2494 mi_1.start = get_slot_ts(Slot::new(1, 7));
2495 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2496 mi_2.start = get_slot_ts(Slot::new(7, 7));
2497 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2498
2499 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2500 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2501 assert_eq!(ms_1, ComponentState::started(Ratio::zero()));
2502 assert_eq!(ms_2, ComponentState::defined());
2503 let mut store = MipStoreRaw::try_from((
2504 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2505 mip_stats_cfg.clone(),
2506 ))
2507 .unwrap();
2508
2509 assert_matches!(
2510 is_consistent(&store, shutdown_start, shutdown_end),
2511 Err(IsConsistentWithShutdownPeriodError::NonConsistent(..))
2512 );
2513 update_store(&mut store, shutdown_start, shutdown_end);
2514 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2515 }
2517
2518 {
2520 mi_1.start = get_slot_ts(Slot::new(9, 2));
2521 mi_1.timeout = get_slot_ts(Slot::new(10, 7));
2522 mi_2.start = get_slot_ts(Slot::new(11, 4));
2523 mi_2.timeout = get_slot_ts(Slot::new(12, 0));
2524
2525 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2526 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2527 assert_eq!(ms_1, ComponentState::started(Ratio::zero()));
2528 assert_eq!(ms_2, ComponentState::defined());
2529 let mut store = MipStoreRaw::try_from((
2530 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2531 mip_stats_cfg.clone(),
2532 ))
2533 .unwrap();
2534
2535 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2536 update_store(&mut store, shutdown_start, shutdown_end);
2537 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2538 }
2540
2541 {
2543 let shutdown_range = shutdown_start..=shutdown_end;
2544
2545 mi_1.start = get_slot_ts(Slot::new(1, 7));
2546 mi_1.timeout = get_slot_ts(Slot::new(5, 7));
2547
2548 let locked_in_at = Slot::new(1, 9);
2550 assert!(locked_in_at < shutdown_start);
2551 let activate_at = Slot::new(4, 0);
2552 assert!(shutdown_range.contains(&activate_at));
2553 mi_1.activation_delay =
2555 get_slot_ts(activate_at).saturating_sub(get_slot_ts(locked_in_at));
2556 let ms_1 = advance_state_until(
2557 ComponentState::locked_in(get_slot_ts(Slot::new(1, 9))),
2558 &mi_1,
2559 );
2560
2561 mi_2.start = get_slot_ts(Slot::new(7, 7));
2563 mi_2.timeout = get_slot_ts(Slot::new(10, 7));
2564 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2565 let mut store = MipStoreRaw::try_from((
2566 [(mi_1.clone(), ms_1), (mi_2.clone(), ms_2)],
2567 mip_stats_cfg.clone(),
2568 ))
2569 .unwrap();
2570
2571 match is_consistent(&store, shutdown_start, shutdown_end) {
2572 Err(IsConsistentWithShutdownPeriodError::NonConsistent(mi, ..)) => {
2573 assert_eq!(mi, mi_1);
2574 }
2575 _ => panic!("is_consistent expects a non consistent error"),
2576 }
2577 update_store(&mut store, shutdown_start, shutdown_end);
2579 assert!(is_consistent(&store, shutdown_start, shutdown_end).is_ok());
2580 store.update_network_version_stats(
2584 get_slot_ts(shutdown_end.get_next_slot(THREAD_COUNT).unwrap()),
2585 Some((1, None)),
2586 );
2587
2588 let (first_mi_info, first_mi_state) = store.store.first_key_value().unwrap();
2589 assert_eq!(*first_mi_info.name, mi_1.name);
2590 assert_eq!(
2592 ComponentStateTypeId::from(&first_mi_state.state),
2593 ComponentStateTypeId::Started
2594 );
2595 let (last_mi_info, last_mi_state) = store.store.last_key_value().unwrap();
2596 assert_eq!(*last_mi_info.name, mi_2.name);
2597 assert_eq!(
2599 ComponentStateTypeId::from(&last_mi_state.state),
2600 ComponentStateTypeId::Defined
2601 );
2602 }
2603 }
2604
2605 #[test]
2606 fn test_started_mips_missing_from_db() {
2607 let genesis_timestamp = MassaTime::from_millis(0);
2612 let get_slot_ts =
2613 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2614
2615 let temp_dir = tempdir().expect("Unable to create a temp folder");
2616 let db_config = MassaDBConfig {
2617 path: temp_dir.path().to_path_buf(),
2618 max_history_length: 100,
2619 max_final_state_elements_size: 100_000,
2620 max_versioning_elements_size: 100_000,
2621 thread_count: THREAD_COUNT,
2622 max_ledger_backups: 10,
2623 enable_metrics: false,
2624 };
2625 let db = Arc::new(RwLock::new(
2626 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2627 ));
2628
2629 let mi_old = MipInfo {
2631 name: "MIP-0002".to_string(),
2632 version: 2,
2633 components: BTreeMap::from([(MipComponent::Address, 1)]),
2634 start: get_slot_ts(Slot::new(2, 0)),
2635 timeout: get_slot_ts(Slot::new(3, 0)),
2636 activation_delay: MassaTime::from_millis(10),
2637 };
2638 let ms_old = advance_state_until(
2639 ComponentState::active(get_slot_ts(Slot::new(2, 5))),
2640 &mi_old,
2641 );
2642 let mi_new = MipInfo {
2644 name: "MIP-0003".to_string(),
2645 version: 3,
2646 components: BTreeMap::from([(MipComponent::Address, 2)]),
2647 start: get_slot_ts(Slot::new(4, 2)),
2648 timeout: get_slot_ts(Slot::new(7, 2)),
2649 activation_delay: MassaTime::from_millis(10),
2650 };
2651 let ms_new = advance_state_until(ComponentState::defined(), &mi_new);
2652
2653 let mip_stats_config = MipStatsConfig {
2654 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2655 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2656 };
2657
2658 let server_store =
2660 MipStore::try_from(([(mi_old.clone(), ms_old)], mip_stats_config.clone()))
2661 .expect("Cannot create the server MIP store");
2662 let mut db_batch = DBBatch::new();
2663 let mut db_versioning_batch = DBBatch::new();
2664 server_store
2665 .update_batches(&mut db_batch, &mut db_versioning_batch, None)
2666 .unwrap();
2667 db.write()
2668 .write_batch(db_batch, db_versioning_batch, Some(Slot::new(3, 0)));
2669
2670 let client_store = MipStore::try_from((
2671 [
2672 (
2673 mi_old.clone(),
2674 advance_state_until(ComponentState::defined(), &mi_old),
2675 ),
2676 (mi_new.clone(), ms_new),
2677 ],
2678 mip_stats_config,
2679 ))
2680 .expect("Cannot create the client MIP store");
2681
2682 let before_start = get_slot_ts(Slot::new(4, 1));
2684 assert!(client_store
2685 .started_mips_missing_from_db(&db, before_start)
2686 .is_empty());
2687
2688 for at in [mi_new.start, get_slot_ts(Slot::new(5, 0))] {
2690 assert_eq!(
2691 client_store.started_mips_missing_from_db(&db, at),
2692 vec![mi_new.clone()]
2693 );
2694 }
2695
2696 assert!(!client_store
2698 .started_mips_missing_from_db(&db, get_slot_ts(Slot::new(9, 0)))
2699 .contains(&mi_old));
2700 }
2701
2702 #[test]
2703 fn test_mip_store_db() {
2704 let genesis_timestamp = MassaTime::from_millis(0);
2712 let get_slot_ts =
2714 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2715
2716 let temp_dir = tempdir().expect("Unable to create a temp folder");
2719 let db_config = MassaDBConfig {
2722 path: temp_dir.path().to_path_buf(),
2723 max_history_length: 100,
2724 max_final_state_elements_size: 100_000,
2725 max_versioning_elements_size: 100_000,
2726 thread_count: THREAD_COUNT,
2727 max_ledger_backups: 10,
2728 enable_metrics: false,
2729 };
2730 let db = Arc::new(RwLock::new(
2731 Box::new(MassaDB::new(db_config)) as Box<dyn MassaDBController + 'static>
2732 ));
2733
2734 let mi_1 = MipInfo {
2737 name: "MIP-0002".to_string(),
2738 version: 2,
2739 components: BTreeMap::from([(MipComponent::Address, 1)]),
2740 start: get_slot_ts(Slot::new(2, 0)),
2741 timeout: get_slot_ts(Slot::new(3, 0)),
2742 activation_delay: MassaTime::from_millis(10),
2743 };
2744 let ms_1 = advance_state_until(ComponentState::defined(), &mi_1);
2745
2746 let mi_2 = MipInfo {
2747 name: "MIP-0003".to_string(),
2748 version: 3,
2749 components: BTreeMap::from([(MipComponent::Address, 2)]),
2750 start: get_slot_ts(Slot::new(4, 2)),
2751 timeout: get_slot_ts(Slot::new(7, 2)),
2752 activation_delay: MassaTime::from_millis(10),
2753 };
2754 let ms_2 = advance_state_until(ComponentState::defined(), &mi_2);
2755
2756 let mip_stats_config = MipStatsConfig {
2757 block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED,
2758 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2759 };
2760 let mut mip_store = MipStore::try_from((
2761 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2762 mip_stats_config.clone(),
2763 ))
2764 .expect("Cannot create an empty MIP store");
2765
2766 mip_store.extend_from_db(db.clone()).unwrap();
2769 assert_eq!(mip_store.0.read().store.len(), 2);
2771 assert_eq!(
2772 mip_store.0.read().store.first_key_value(),
2773 Some((&mi_1, &ms_1))
2774 );
2775 assert_eq!(
2776 mip_store.0.read().store.last_key_value(),
2777 Some((&mi_2, &ms_2))
2778 );
2779
2780 let active_at = get_slot_ts(Slot::new(2, 5));
2782 let ms_1_ = advance_state_until(ComponentState::active(active_at), &mi_1);
2783
2784 let mip_store_ =
2785 MipStore::try_from(([(mi_1.clone(), ms_1_.clone())], mip_stats_config.clone()))
2786 .expect("Cannot create an empty MIP store");
2787
2788 let (updated, added) = mip_store.update_with(&mip_store_).unwrap();
2789
2790 assert_eq!(updated.len(), 1);
2792 assert_eq!(added.len(), 0);
2793 assert_eq!(mip_store.0.read().store.len(), 2);
2794 assert_eq!(
2795 mip_store.0.read().store.first_key_value(),
2796 Some((&mi_1, &ms_1_))
2797 );
2798 assert_eq!(
2799 mip_store.0.read().store.last_key_value(),
2800 Some((&mi_2, &ms_2))
2801 );
2802
2803 let mut db_batch = DBBatch::new();
2806 let mut db_versioning_batch = DBBatch::new();
2807
2808 let slot_bounds_ = (&Slot::new(1, 0), &Slot::new(4, 2));
2810 let between = (&get_slot_ts(*slot_bounds_.0), &get_slot_ts(*slot_bounds_.1));
2811
2812 mip_store
2813 .update_batches(&mut db_batch, &mut db_versioning_batch, Some(between))
2814 .unwrap();
2815
2816 assert_eq!(db_batch.len(), 1); assert_eq!(db_versioning_batch.len(), 3); let mut guard_db = db.write();
2820 guard_db.write_batch(db_batch, db_versioning_batch, Some(Slot::new(3, 0)));
2822 drop(guard_db);
2823
2824 let mut mip_store_2 = MipStore::try_from((
2826 [(mi_1.clone(), ms_1.clone()), (mi_2.clone(), ms_2.clone())],
2827 mip_stats_config.clone(),
2828 ))
2829 .expect("Cannot create an empty MIP store");
2830 mip_store_2.extend_from_db(db.clone()).unwrap();
2833
2834 let guard_1 = mip_store.0.read();
2835 let guard_2 = mip_store_2.0.read();
2836 let st1_raw = guard_1.deref();
2837 let st2_raw = guard_2.deref();
2838
2839 assert_eq!(st1_raw, st2_raw);
2842
2843 let mut count = 0;
2845 for (ser_key, ser_value) in db
2846 .read()
2847 .iterator_cf_for_full_db_traversal(STATE_CF, MassaIteratorMode::Start)
2848 {
2849 assert!(mip_store.is_key_value_valid(&ser_key, &ser_value));
2850 count += 1;
2851 }
2852 assert_gt!(count, 0);
2853
2854 let mut count2 = 0;
2855 for (ser_key, ser_value) in db
2856 .read()
2857 .iterator_cf_for_full_db_traversal(VERSIONING_CF, MassaIteratorMode::Start)
2858 {
2859 assert!(mip_store.is_key_value_valid(&ser_key, &ser_value));
2860 count2 += 1;
2861 }
2862 assert_gt!(count2, 0);
2863 }
2864
2865 #[test]
2866 fn test_mip_store_stats() {
2867 let genesis_timestamp = MassaTime::from_millis(0);
2871 let get_slot_ts =
2872 |slot| get_block_slot_timestamp(THREAD_COUNT, T0, genesis_timestamp, slot).unwrap();
2873
2874 let mip_stats_config = MipStatsConfig {
2875 block_count_considered: 2,
2876 warn_announced_version_ratio: Ratio::new_raw(30, 100),
2877 };
2878 let activation_delay = MassaTime::from_millis(100);
2879 let timeout = MassaTime::now().saturating_add(MassaTime::from_millis(50_000)); let mi_1 = MipInfo {
2881 name: "MIP-0001".to_string(),
2882 version: 1,
2883 components: BTreeMap::from([(MipComponent::Address, 1)]),
2884 start: MassaTime::from_millis(2),
2885 timeout,
2886 activation_delay,
2887 };
2888 let ms_1 = advance_state_until(ComponentState::started(Ratio::zero()), &mi_1);
2889
2890 let mut mip_store =
2891 MipStoreRaw::try_from(([(mi_1.clone(), ms_1)], mip_stats_config)).unwrap();
2892
2893 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((0, Some(1))));
2895 assert_eq!(mip_store.stats.network_version_counters.len(), 1);
2896 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&1));
2897
2898 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((0, Some(1))));
2899 assert_eq!(mip_store.stats.network_version_counters.len(), 1);
2900 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&2));
2901
2902 let (mi_, ms_) = mip_store.store.last_key_value().unwrap();
2904 assert_eq!(*mi_, mi_1);
2905 assert_matches!(ms_.state, ComponentState::LockedIn(..));
2906
2907 let mut at = MassaTime::now();
2908 at = at.saturating_add(activation_delay);
2909 assert_eq!(
2910 ms_.state_at(at, mi_1.start, mi_1.timeout, mi_1.activation_delay),
2911 Ok(ComponentStateTypeId::Active)
2912 );
2913
2914 mip_store.update_network_version_stats(get_slot_ts(Slot::new(1, 0)), Some((1, Some(2))));
2916 assert_eq!(mip_store.stats.network_version_counters.len(), 2);
2918 assert_eq!(mip_store.stats.network_version_counters.get(&1), Some(&1));
2920 assert_eq!(mip_store.stats.network_version_counters.get(&2), Some(&1));
2921 }
2922}