massa_versioning/
versioning_factory.rs

1use massa_time::MassaTime;
2use std::collections::BTreeMap;
3use thiserror::Error;
4
5use crate::versioning::{ComponentStateTypeId, MipComponent, MipStore};
6
7/// Factory error
8#[allow(missing_docs)]
9#[derive(Error, Debug, Clone)]
10pub enum FactoryError {
11    #[error("Unknown version, cannot build obj with version: {0}")]
12    UnknownVersion(u32),
13    #[error("Version: {0} is in versioning store but not yet implemented")]
14    UnimplementedVersion(u32), // programmer error, forgot to implement new version in factory traits
15    #[error("Version: {0} is not active yet")]
16    OnStateNotReady(u32),
17    #[error("Could not create object of type {0}: {1}")]
18    OnCreate(String, String),
19}
20
21#[derive(Clone, Debug)]
22/// Strategy to use when creating a new object from a factory
23pub enum FactoryStrategy {
24    /// use get_latest_version (see Factory trait)
25    // Latest,
26    /// Require to create an object with this specific version
27    Exact(u32),
28    /// Create an object given a timestamp (e.g slot)
29    At(MassaTime),
30}
31
32impl From<u32> for FactoryStrategy {
33    fn from(value: u32) -> Self {
34        FactoryStrategy::Exact(value)
35    }
36}
37
38// Factory traits
39
40/// Trait for Factory that create objects based on versioning store
41pub trait VersioningFactory {
42    /// Factory will create only one object of a given type,
43    /// so a FactoryAddress (impl VersioningFactory), will have type Output = Address
44    type Output;
45    /// Error if object cannot be created (+ used for VersioningFactoryArgs)
46    type Error;
47    /// Arguments struct in order to create Self::Output
48    type Arguments;
49
50    /// Return the MipComponent associated with return type (Output)
51    /// e.g. if type Output = Address, should return MipComponent::Address
52    fn get_component() -> MipComponent;
53    /// Access to the MipStore
54    fn get_versioning_store(&self) -> MipStore;
55
56    /// Get latest version at given timestamp (e.g. slot)
57    fn get_latest_component_version_at(&self, ts: MassaTime) -> Result<u32, FactoryError> {
58        let component = Self::get_component();
59        let mi_store_ = self.get_versioning_store();
60        Ok(mi_store_.get_latest_component_version_at(&component, ts))
61    }
62
63    /// Get all versions in 'Active state' for the associated MipComponent
64    fn get_all_active_component_versions(&self) -> Vec<u32> {
65        let component = Self::get_component();
66        let mip_store = self.get_versioning_store();
67        mip_store.get_all_active_component_versions(&component)
68    }
69
70    /// Get all versions (at any state) for the associated MipComponent
71    fn get_all_component_versions(&self) -> BTreeMap<u32, ComponentStateTypeId> {
72        let component = Self::get_component();
73        let mip_store = self.get_versioning_store();
74        mip_store.get_all_component_versions(&component)
75    }
76
77    /// Get the version the current component with the given strategy
78    fn get_component_version_with_strategy(
79        &self,
80        strategy: FactoryStrategy,
81    ) -> Result<u32, FactoryError> {
82        match strategy {
83            FactoryStrategy::Exact(v) => match self.get_all_component_versions().get(&v) {
84                Some(s) if *s == ComponentStateTypeId::Active => Ok(v),
85                Some(s) if *s != ComponentStateTypeId::Active => {
86                    Err(FactoryError::OnStateNotReady(v))
87                }
88                _ => Err(FactoryError::UnknownVersion(v)),
89            },
90            FactoryStrategy::At(ts) => self.get_latest_component_version_at(ts),
91        }
92    }
93
94    /// Create an object of type Self::Output
95    fn create(
96        &self,
97        args: &Self::Arguments,
98        strategy: FactoryStrategy,
99    ) -> Result<Self::Output, Self::Error>;
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105
106    use num::rational::Ratio;
107    use std::collections::BTreeMap;
108
109    use crate::test_helpers::versioning_helpers::advance_state_until;
110    use crate::versioning::{ComponentState, MipInfo, MipState, MipStatsConfig};
111
112    use massa_time::MassaTime;
113
114    // Define a struct Address with 2 versions AddressV0 & AddressV1
115    #[allow(dead_code)]
116    #[derive(Debug)]
117    struct TestAddressV0 {
118        hash: String,
119    }
120
121    #[allow(dead_code)]
122    impl TestAddressV0 {
123        fn new(hash: String) -> Self {
124            Self { hash }
125        }
126    }
127
128    #[allow(dead_code)]
129    #[derive(Debug)]
130    struct TestAddressV1 {
131        slot: String,
132        creator: String,
133        index: u32,
134    }
135
136    #[allow(dead_code)]
137    impl TestAddressV1 {
138        fn new(slot: String, creator: String, index: u32) -> Self {
139            Self {
140                slot,
141                creator,
142                index,
143            }
144        }
145    }
146
147    #[allow(dead_code)]
148    #[derive(Debug)]
149    enum TestAddress {
150        V0(TestAddressV0),
151        V1(TestAddressV1),
152    }
153
154    struct TestAddressArgs {
155        // V0
156        hash: Option<String>,
157        // V1
158        slot: Option<String>,
159        creator: Option<String>,
160        index: Option<u32>,
161    }
162
163    // Now we define an Address factory
164
165    #[derive(Debug)]
166    struct TestAddressFactory {
167        versioning_store: MipStore,
168    }
169
170    impl VersioningFactory for TestAddressFactory {
171        type Output = TestAddress;
172        type Error = FactoryError;
173        type Arguments = TestAddressArgs;
174
175        fn get_component() -> MipComponent {
176            MipComponent::Address
177        }
178
179        fn get_versioning_store(&self) -> MipStore {
180            self.versioning_store.clone()
181        }
182
183        fn create(
184            &self,
185            args: &Self::Arguments,
186            strategy: FactoryStrategy,
187        ) -> Result<Self::Output, Self::Error> {
188            let version = self.get_component_version_with_strategy(strategy);
189
190            match version {
191                Ok(0) => Ok(TestAddress::V0(TestAddressV0 {
192                    hash: args.hash.clone().ok_or(FactoryError::OnCreate(
193                        stringify!(Self::Output).to_string(),
194                        "Please provide hash in args".to_string(),
195                    ))?,
196                })),
197                Ok(1) => Ok(TestAddress::V1(TestAddressV1 {
198                    slot: args.slot.clone().ok_or(FactoryError::OnCreate(
199                        stringify!(Self::Output).to_string(),
200                        "Please provide 'slot' in args".to_string(),
201                    ))?,
202                    creator: args.creator.clone().unwrap(),
203                    index: args.index.unwrap(),
204                })),
205                Ok(v) => Err(FactoryError::UnimplementedVersion(v)),
206                Err(e) => Err(e),
207            }
208        }
209    }
210
211    #[test]
212    fn test_address_factory_args() {
213        let vi_1 = MipInfo {
214            name: "MIP-0002".to_string(),
215            version: 1,
216            components: BTreeMap::from([(MipComponent::Address, 1)]),
217            start: MassaTime::from_millis(12),
218            timeout: MassaTime::from_millis(15),
219            activation_delay: MassaTime::from_millis(2),
220        };
221        let vs_1 = MipState::new(MassaTime::from_millis(10));
222
223        let vi_2 = MipInfo {
224            name: "MIP-0003".to_string(),
225            version: 2,
226            components: BTreeMap::from([(MipComponent::Address, 2)]),
227            start: MassaTime::from_millis(25),
228            timeout: MassaTime::from_millis(28),
229            activation_delay: MassaTime::from_millis(2),
230        };
231        let vs_2 = MipState::new(MassaTime::from_millis(18));
232
233        let mip_stats_cfg = MipStatsConfig {
234            block_count_considered: 10,
235            warn_announced_version_ratio: Ratio::new_raw(30, 100),
236        };
237
238        let vs = MipStore::try_from((
239            [(vi_1.clone(), vs_1), (vi_2.clone(), vs_2.clone())],
240            mip_stats_cfg,
241        ))
242        .unwrap();
243        let fa = TestAddressFactory {
244            versioning_store: vs.clone(),
245        };
246
247        let args = TestAddressArgs {
248            hash: Some("sdofjsklfhskfjl".into()),
249            slot: Some("slot_4_2".to_string()),
250            creator: Some("me_pubk".to_string()),
251            index: Some(3),
252        };
253        let args_no_v1 = TestAddressArgs {
254            hash: Some("sdofjsklfhskfjl".into()),
255            slot: None,
256            creator: Some("me_pubk".to_string()),
257            index: Some(3),
258        };
259
260        assert_eq!(fa.get_all_active_component_versions(), vec![0]);
261
262        let addr_a = fa.create(&args, 0.into());
263        assert!(matches!(addr_a, Ok(TestAddress::V0(_))));
264        //
265        // Version 2 is unknown
266        let addr_ = fa.create(&args, 2.into());
267        assert!(matches!(addr_, Err(FactoryError::OnStateNotReady(2))));
268
269        // Advance state 1 to Active
270        let _time = MassaTime::now();
271        let vs_1_new = advance_state_until(ComponentState::active(_time), &vi_1);
272        // Create a new factory
273        let info = BTreeMap::from([(vi_1.clone(), vs_1_new.clone()), (vi_2.clone(), vs_2)]);
274        // Update versioning store
275        vs.0.write().store = info;
276
277        assert_eq!(fa.get_all_active_component_versions(), vec![0, 1]);
278        assert_eq!(
279            fa.get_all_component_versions()
280                .keys()
281                .cloned()
282                .collect::<Vec<u32>>(),
283            vec![0, 1, 2]
284        );
285        // assert_eq!(fa.get_latest_component_version(), 1);
286        let addr_b = fa.create(&args, FactoryStrategy::At(MassaTime::now()));
287        assert!(matches!(addr_b, Ok(TestAddress::V1(_))));
288
289        // Error if not enough args
290        let addr_ = fa.create(&args_no_v1, 1.into());
291        assert!(matches!(addr_, Err(FactoryError::OnCreate(_, _))));
292
293        // Can still create AddressV0
294        let addr_c = fa.create(&args, 0.into());
295        println!("addr_c: {:?}", addr_c);
296        assert!(matches!(addr_c, Ok(TestAddress::V0(_))));
297    }
298
299    #[test]
300    fn test_factory_strategy_at() {
301        // Test factory & FactoryStrategy::At(...)
302
303        let _time = MassaTime::now();
304        let vi_1 = MipInfo {
305            name: "MIP-0002".to_string(),
306            version: 1,
307            components: BTreeMap::from([(MipComponent::Address, 1)]),
308            start: MassaTime::from_millis(12),
309            timeout: MassaTime::from_millis(15),
310            activation_delay: MassaTime::from_millis(2),
311        };
312        let vs_1 = advance_state_until(ComponentState::active(_time), &vi_1);
313
314        let vi_2 = MipInfo {
315            name: "MIP-0003".to_string(),
316            version: 2,
317            components: BTreeMap::from([(MipComponent::Address, 2)]),
318            start: MassaTime::from_millis(25),
319            timeout: MassaTime::from_millis(28),
320            activation_delay: MassaTime::from_millis(2),
321        };
322        let vs_2 = MipState::new(MassaTime::from_millis(18));
323
324        let mip_stats_cfg = MipStatsConfig {
325            block_count_considered: 10,
326            warn_announced_version_ratio: Ratio::new_raw(30, 100),
327        };
328
329        let vs = MipStore::try_from((
330            [(vi_1.clone(), vs_1.clone()), (vi_2.clone(), vs_2.clone())],
331            mip_stats_cfg,
332        ))
333        .unwrap();
334
335        let fa = TestAddressFactory {
336            versioning_store: vs.clone(),
337        };
338
339        let args = TestAddressArgs {
340            hash: Some("sdofjsklfhskfjl".into()),
341            slot: Some("slot_4_2".to_string()),
342            creator: Some("me_pubk".to_string()),
343            index: Some(3),
344        };
345
346        //
347        let st_1 = FactoryStrategy::At(MassaTime::from_millis(8)); // vi_1 not yet defined
348        let ts_1_2 = MassaTime::from_millis(13);
349        let st_1_2 = FactoryStrategy::At(ts_1_2); // vi_1 is started (after vi_1.start)
350        let st_2 = FactoryStrategy::At(MassaTime::from_millis(18)); // vi_1 is active (after start + activation delay)
351        let st_3 = FactoryStrategy::At(MassaTime::from_millis(27)); // vi_2 is started or locked_in
352        let st_4 = FactoryStrategy::At(MassaTime::from_millis(30)); // vi_2 is active (after vi_2.timeout)
353
354        let addr_st_1 = fa.create(&args, st_1);
355        let addr_st_1_2 = fa.create(&args, st_1_2.clone());
356        let addr_st_2 = fa.create(&args, st_2);
357        let addr_st_3 = fa.create(&args, st_3);
358        let addr_st_4 = fa.create(&args, st_4.clone());
359
360        assert!(matches!(addr_st_1, Ok(TestAddress::V0(_))));
361        assert!(matches!(addr_st_1_2, Ok(TestAddress::V0(_))));
362        assert!(matches!(addr_st_2, Ok(TestAddress::V1(_))));
363        assert!(matches!(addr_st_3, Ok(TestAddress::V1(_))));
364        assert!(matches!(addr_st_4, Ok(TestAddress::V1(_)))); // for now, vs_2 is not active yet
365
366        // Advance state 2 to Active
367        let vs_2_new = advance_state_until(ComponentState::active(_time), &vi_2);
368        let info = BTreeMap::from([(vi_1.clone(), vs_1), (vi_2.clone(), vs_2_new)]);
369        // Update versioning store
370        vs.0.write().store = info;
371
372        assert_eq!(fa.get_all_active_component_versions(), vec![0, 1, 2]);
373        let addr_st_4 = fa.create(&args, st_4);
374        // Version 2 is selected but this is not implemented in factory yet
375        assert!(matches!(
376            addr_st_4,
377            Err(FactoryError::UnimplementedVersion(2))
378        ));
379    }
380}