diff --git a/massa-versioning-worker/src/versioning.rs b/massa-versioning-worker/src/versioning.rs index 0bb65d4ce33..5916779c809 100644 --- a/massa-versioning-worker/src/versioning.rs +++ b/massa-versioning-worker/src/versioning.rs @@ -30,19 +30,10 @@ pub struct MipInfo { pub name: String, /// Network (or global) version (to be included in block header) pub version: u32, - /// Component concerned by this versioning (e.g. a new Block version) - pub component: MipComponent, - /// Component version - pub component_version: u32, - - /// a timestamp at which the version gains its meaning (e.g. accepted in block header) - // pub start: MassaTime, - /// a timestamp at which the deployment is considered active or failed (timeout > start) - // pub timeout: MassaTime, - + /// Components concerned by this versioning (e.g. a new Block version), and the associated component_version + pub components: HashMap, /// a timestamp at which the version gains its meaning (e.g. announced in block header) pub start: MassaTime, - // TODO: rename to start_timeout? /// a timestamp at the which the deployment is considered failed pub timeout: MassaTime, /// Once deployment has been locked, wait for this duration before deployment is considered active @@ -67,7 +58,7 @@ impl PartialEq for MipInfo { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.version == other.version - && self.component == other.component + && self.components == other.components && self.start == other.start && self.timeout == other.timeout && self.activation_delay == other.activation_delay @@ -81,7 +72,7 @@ impl Hash for MipInfo { fn hash(&self, state: &mut H) { self.name.hash(state); self.version.hash(state); - self.component.hash(state); + self.components.iter().for_each(|c| c.hash(state)); self.start.hash(state); self.timeout.hash(state); } @@ -498,7 +489,13 @@ impl MipStoreRaw { let mut component_versions: HashMap = self .0 .iter() - .map(|i| (i.0.component.clone(), i.0.component_version)) + .flat_map(|c| { + c.0.components + .iter() + .map(|(mip_component, component_version)| { + (mip_component.clone(), *component_version) + }) + }) .collect(); let mut names: BTreeSet = self.0.iter().map(|i| i.0.name.clone()).collect(); let mut to_update: BTreeMap = Default::default(); @@ -544,18 +541,27 @@ impl MipStoreRaw { .or(self.0.last_key_value().map(|i| i.0)); if let Some(last_v_info) = last_v_info_ { + // check for versions of all components in v_info + let mut component_version_compatible = true; + for component in v_info.components.iter() { + if component.1 <= component_versions.get(component.0).unwrap_or(&0) { + component_version_compatible = false; + break; + } + } + if v_info.start > last_v_info.timeout && v_info.timeout > v_info.start && v_info.version > last_v_info.version && !names.contains(&v_info.name) - && v_info.component_version - > *component_versions.get(&v_info.component).unwrap_or(&0) + && component_version_compatible { // Time range is ok / version is ok / name is unique, let's add it to_add.insert(v_info.clone(), v_state.clone()); names.insert(v_info.name.clone()); - component_versions - .insert(v_info.component.clone(), v_info.component_version); + for component in v_info.components.iter() { + component_versions.insert(component.0.clone(), *component.1); + } } else { // Something is wrong (time range not ok? / version not incr? / names? // or component version not incr?) @@ -652,8 +658,7 @@ mod test { MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(start.timestamp() as u64), timeout: MassaTime::from(timeout.timestamp() as u64), activation_delay: MassaTime::from(20), @@ -893,8 +898,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(2), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -903,8 +907,7 @@ mod test { let vi_2 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(7), timeout: MassaTime::from(10), activation_delay: MassaTime::from(2), @@ -960,8 +963,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(2), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -973,8 +975,7 @@ mod test { let vi_2 = MipInfo { name: "MIP-0003".to_string(), version: 3, - component: MipComponent::Address, - component_version: 2, + components: HashMap::from([(MipComponent::Address, 2)]), start: MassaTime::from(17), timeout: MassaTime::from(27), activation_delay: MassaTime::from(2), @@ -1008,8 +1009,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(0), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -1020,8 +1020,7 @@ mod test { let vi_2 = MipInfo { name: "MIP-0003".to_string(), version: 3, - component: MipComponent::Address, - component_version: 2, + components: HashMap::from([(MipComponent::Address, 2)]), start: MassaTime::from(17), timeout: MassaTime::from(27), activation_delay: MassaTime::from(2), @@ -1059,7 +1058,7 @@ mod test { .unwrap(); let mut vi_2_2 = vi_2.clone(); - vi_2_2.component_version = vi_1.component_version; + vi_2_2.components = vi_1.components.clone(); let vs_2_2 = advance_state_until(ComponentState::defined(), &vi_2_2); let vs_raw_2 = MipStoreRaw(BTreeMap::from([ diff --git a/massa-versioning-worker/src/versioning_factory.rs b/massa-versioning-worker/src/versioning_factory.rs index e0af3c036fa..e280a93efc2 100644 --- a/massa-versioning-worker/src/versioning_factory.rs +++ b/massa-versioning-worker/src/versioning_factory.rs @@ -66,8 +66,11 @@ pub trait VersioningFactory { .iter() .rev() .find_map(|(vi, vsh)| { - (vi.component == component && vsh.inner == state_active) - .then_some(vi.component_version) + if vsh.inner == state_active { + vi.components.get(&component).copied() + } else { + None + } }) .unwrap_or(0) } @@ -84,11 +87,13 @@ pub trait VersioningFactory { .0 .iter() .rev() - .filter(|(vi, vsh)| vi.component == component && vsh.inner == state_active) + .filter(|(vi, vsh)| { + vi.components.get(&component).is_some() && vsh.inner == state_active + }) .find_map(|(vi, vsh)| { let res = vsh.state_at(ts, vi.start, vi.timeout); match res { - Ok(ComponentStateTypeId::Active) => Some(vi.component_version), + Ok(ComponentStateTypeId::Active) => vi.components.get(&component).copied(), _ => None, } }) @@ -105,7 +110,11 @@ pub trait VersioningFactory { let state_active = ComponentState::active(); let versions_iter = vi_store.0.iter().filter_map(|(vi, vsh)| { - (vi.component == component && vsh.inner == state_active).then_some(vi.component_version) + if vsh.inner == state_active { + vi.components.get(&component).copied() + } else { + None + } }); let versions: Vec = iter::once(0).chain(versions_iter).collect(); versions @@ -118,8 +127,12 @@ pub trait VersioningFactory { let vi_store = vi_store_.0.read(); let versions_iter = vi_store.0.iter().filter_map(|(vi, vsh)| { - (vi.component == component) - .then_some((vi.component_version, ComponentStateTypeId::from(&vsh.inner))) + vi.components + .get(&component) + .copied() + .map(|component_version| { + (component_version, ComponentStateTypeId::from(&vsh.inner)) + }) }); iter::once((0, ComponentStateTypeId::Active)) .chain(versions_iter) @@ -138,7 +151,7 @@ pub trait VersioningFactory { mod test { use super::*; - use std::collections::BTreeMap; + use std::collections::{BTreeMap, HashMap}; use crate::test_helpers::versioning_helpers::advance_state_until; use crate::versioning::{MipInfo, MipState}; @@ -260,8 +273,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 1, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(12), timeout: MassaTime::from(15), activation_delay: MassaTime::from(2), @@ -271,8 +283,7 @@ mod test { let vi_2 = MipInfo { name: "MIP-0003".to_string(), version: 2, - component: MipComponent::Address, - component_version: 2, + components: HashMap::from([(MipComponent::Address, 2)]), start: MassaTime::from(25), timeout: MassaTime::from(28), activation_delay: MassaTime::from(2), @@ -343,8 +354,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 1, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(12), timeout: MassaTime::from(15), activation_delay: MassaTime::from(2), @@ -354,8 +364,7 @@ mod test { let vi_2 = MipInfo { name: "MIP-0003".to_string(), version: 2, - component: MipComponent::Address, - component_version: 2, + components: HashMap::from([(MipComponent::Address, 2)]), start: MassaTime::from(25), timeout: MassaTime::from(28), activation_delay: MassaTime::from(2), diff --git a/massa-versioning-worker/src/versioning_ser_der.rs b/massa-versioning-worker/src/versioning_ser_der.rs index d3ffc5b10d4..107d24e6002 100644 --- a/massa-versioning-worker/src/versioning_ser_der.rs +++ b/massa-versioning-worker/src/versioning_ser_der.rs @@ -24,6 +24,7 @@ use massa_time::{MassaTimeDeserializer, MassaTimeSerializer}; /// Ser / Der const MIP_INFO_NAME_MAX_LEN: u32 = 255; +const MIP_INFO_COMPONENTS_MAX_ENTRIES: u32 = 8; const COMPONENT_STATE_VARIANT_COUNT: u32 = mem::variant_count::() as u32; const COMPONENT_STATE_ID_VARIANT_COUNT: u32 = mem::variant_count::() as u32; const MIP_STORE_MAX_ENTRIES: u32 = 4096; @@ -64,7 +65,7 @@ impl Serializer for MipInfoSerializer { } let name_len = u32::try_from(name_len_).map_err(|_| { SerializeError::GeneralError(format!( - "Cannot convert to name_len: {} to u64", + "Cannot convert to name_len: {} to u32", name_len_ )) })?; @@ -72,14 +73,32 @@ impl Serializer for MipInfoSerializer { buffer.extend(value.name.as_bytes()); // version self.u32_serializer.serialize(&value.version, buffer)?; - // component - let component_ = value.component.clone(); - let component: u32 = component_.into(); - - self.u32_serializer.serialize(&component, buffer)?; - // component version - self.u32_serializer - .serialize(&value.component_version, buffer)?; + + // Components + let components_len_ = value.components.len(); + if components_len_ > MIP_INFO_COMPONENTS_MAX_ENTRIES as usize { + return Err(SerializeError::NumberTooBig(format!( + "MIP info cannot have more than {} components, got: {}", + MIP_STORE_MAX_ENTRIES, components_len_ + ))); + } + let components_len = u32::try_from(components_len_).map_err(|_| { + SerializeError::GeneralError(format!( + "Cannot convert to component_len: {} to u32", + name_len_ + )) + })?; + // ser hashmap len + self.u32_serializer.serialize(&components_len, buffer)?; + // ser hashmap items + for (component, component_version) in value.components.iter() { + // component + self.u32_serializer + .serialize(&component.clone().into(), buffer)?; + // component version + self.u32_serializer.serialize(component_version, buffer)?; + } + // start self.time_serializer.serialize(&value.start, buffer)?; // timeout @@ -147,19 +166,30 @@ impl Deserializer for MipInfoDeserializer { context("Failed version deserialization", |input| { self.u32_deserializer.deserialize(input) }), - context("Failed component deserialization", |input| { - let (rem, component_) = self.u32_deserializer.deserialize(input)?; - let component = MipComponent::try_from(component_).map_err(|_| { - nom::Err::Error(ParseError::from_error_kind( - input, - nom::error::ErrorKind::Fail, - )) - })?; - IResult::Ok((rem, component)) - }), - context("Failed component version deserialization", |input| { - self.u32_deserializer.deserialize(input) - }), + context( + "Failed components deserialization", + length_count( + context("Failed components length deserialization", |input| { + self.u32_deserializer.deserialize(input) + }), + tuple(( + context("Failed component deserialization", |input| { + let (rem, component_) = self.u32_deserializer.deserialize(input)?; + let component = + MipComponent::try_from(component_).map_err(|_| { + nom::Err::Error(ParseError::from_error_kind( + input, + nom::error::ErrorKind::Fail, + )) + })?; + IResult::Ok((rem, component)) + }), + context("Failed component version deserialization", |input| { + self.u32_deserializer.deserialize(input) + }), + )), + ), + ), context("Failed start deserialization", |input| { self.time_deserializer.deserialize(input) }), @@ -172,16 +202,13 @@ impl Deserializer for MipInfoDeserializer { )), ) .map( - |(name, version, component, component_version, start, timeout, activation_delay)| { - MipInfo { - name, - version, - component, - component_version, - start, - timeout, - activation_delay, - } + |(name, version, components, start, timeout, activation_delay)| MipInfo { + name, + version, + components: components.into_iter().collect(), + start, + timeout, + activation_delay, }, ) .parse(buffer) @@ -665,13 +692,16 @@ impl Deserializer for MipStoreRawDeserializer { #[cfg(test)] mod test { use super::*; + + use std::collections::HashMap; use std::mem::{size_of, size_of_val}; + use std::str::FromStr; use chrono::{NaiveDate, NaiveDateTime}; use more_asserts::assert_lt; - use std::str::FromStr; use crate::test_helpers::versioning_helpers::advance_state_until; + use massa_serialization::DeserializeError; use massa_time::MassaTime; @@ -680,8 +710,7 @@ mod test { let vi_1 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(2), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -781,8 +810,7 @@ mod test { let mi_1 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(2), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -801,8 +829,7 @@ mod test { let mi_2 = MipInfo { name: "MIP-0002".to_string(), version: 2, - component: MipComponent::Address, - component_version: 1, + components: HashMap::from([(MipComponent::Address, 1)]), start: MassaTime::from(2), timeout: MassaTime::from(5), activation_delay: MassaTime::from(2), @@ -811,8 +838,7 @@ mod test { let mi_3 = MipInfo { name: "MIP-0003".to_string(), version: 3, - component: MipComponent::Block, - component_version: 1, + components: HashMap::from([(MipComponent::Block, 1)]), start: MassaTime::from(12), timeout: MassaTime::from(17), activation_delay: MassaTime::from(2), @@ -842,17 +868,16 @@ mod test { let mut mi_base = MipInfo { name: "A".repeat(254), version: 0, - component: MipComponent::Address, - component_version: 0, + components: HashMap::from([(MipComponent::Address, 0)]), start: MassaTime::from(0), timeout: MassaTime::from(2), activation_delay: MassaTime::from(2), }; + // Note: we did not add the name ptr and hashmap ptr, only the data inside let mi_base_size = size_of_val(&mi_base.name[..]) + size_of_val(&mi_base.version) - + size_of_val(&mi_base.component) - + size_of_val(&mi_base.component_version) + + mi_base.components.len() * size_of::() * 2 + size_of_val(&mi_base.start) + size_of_val(&mi_base.timeout); @@ -861,7 +886,10 @@ mod test { let store_raw_: Vec<(MipInfo, MipState)> = (0..MIP_STORE_MAX_ENTRIES) .map(|_i| { mi_base.version += 1; - mi_base.component_version += 1; + mi_base + .components + .entry(MipComponent::Address) + .and_modify(|e| *e += 1); mi_base.start = mi_base.timeout.saturating_add(MassaTime::from(1)); mi_base.timeout = mi_base.start.saturating_add(MassaTime::from(2));