version.rs
1 //! Fedimint consensus and API versioning. 2 //! 3 //! ## Introduction 4 //! 5 //! Fedimint federations are expected to last and serve over time diverse set of 6 //! clients running on various devices and platforms with different 7 //! versions of the client software. To ensure broad interoperability core 8 //! Fedimint logic and modules use consensus and API version scheme. 9 //! 10 //! ## Definitions 11 //! 12 //! * Fedimint *component* - either a core Fedimint logic or one of the modules 13 //! 14 //! ## Consensus versions 15 //! 16 //! By definition all instances of a given component on every peer inside a 17 //! Federation must be running with the same consensus version at the same time. 18 //! 19 //! Each component in the Federation can only ever be in one consensus version. 20 //! The set of all consensus versions of each component is a part of consensus 21 //! config that is identical for all peers. 22 //! 23 //! The code implementing given component can however support multiple consensus 24 //! versions at the same time, making it possible to use the same code for 25 //! diverse set of Federations created at different times. The consensus 26 //! version to run with is passed to the code during initialization. 27 //! 28 //! The client side components need track consensus versions of each Federation 29 //! they use and be able to handle the currently running version of it. 30 //! 31 //! [`CoreConsensusVersion`] and [`ModuleConsensusVersion`] are used for 32 //! consensus versioning. 33 //! 34 //! ## API versions 35 //! 36 //! Unlike consensus version which has to be single and identical across 37 //! Federation, both server and client side components can advertise 38 //! simultaneous support for multiple API versions. This is the main mechanism 39 //! to ensure interoperability in the face of hard to control and predict 40 //! software changes across all the involved software. 41 //! 42 //! Each peer in the Federation and each client can update the Fedimint software 43 //! at their own pace without coordinating API changes. 44 //! 45 //! Each client is expected to survey Federation API support and discover the 46 //! API version to use for each component. 47 //! 48 //! Notably the current consensus version of a software component is considered 49 //! a prefix to the API version it advertises. 50 //! 51 //! Software components implementations are expected to provide a good multi-API 52 //! support to ensure clients and Federations can always find common API 53 //! versions to use. 54 //! 55 //! [`ApiVersion`] and [`MultiApiVersion`] is used for API versioning. 56 use std::collections::BTreeMap; 57 use std::{cmp, result}; 58 59 use serde::{Deserialize, Serialize}; 60 61 use crate::core::{ModuleInstanceId, ModuleKind}; 62 use crate::db::DatabaseVersion; 63 use crate::encoding::{Decodable, Encodable}; 64 65 /// Consensus version of a core server 66 /// 67 /// Breaking changes in the Fedimint's core consensus require incrementing it. 68 /// 69 /// See [`ModuleConsensusVersion`] for more details on how it interacts with 70 /// module's consensus. 71 #[derive(Debug, Copy, Clone, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq)] 72 pub struct CoreConsensusVersion { 73 pub major: u32, 74 pub minor: u32, 75 } 76 77 impl CoreConsensusVersion { 78 pub const fn new(major: u32, minor: u32) -> Self { 79 Self { major, minor } 80 } 81 } 82 83 /// Globally declared core consensus version 84 pub const CORE_CONSENSUS_VERSION: CoreConsensusVersion = CoreConsensusVersion::new(2, 0); 85 86 /// Consensus version of a specific module instance 87 /// 88 /// Any breaking change to the module's consensus rules require incrementing the 89 /// major part of it. 90 /// 91 /// Any backwards-compatible changes with regards to clients require 92 /// incrementing the minor part of it. Backwards compatible changes will 93 /// typically be introducing new input/output/consensus item variants that old 94 /// clients won't understand but can safely ignore while new clients can use new 95 /// functionality. It's akin to soft forks in Bitcoin. 96 /// 97 /// A module instance can run only in one consensus version, which must be the 98 /// same (both major and minor) across all corresponding instances on other 99 /// nodes of the federation. 100 /// 101 /// When [`CoreConsensusVersion`] changes, this can but is not requires to be 102 /// a breaking change for each module's [`ModuleConsensusVersion`]. 103 /// 104 /// For many modules it might be preferable to implement a new 105 /// [`fedimint_core::core::ModuleKind`] "versions" (to be implemented at the 106 /// time of writing this comment), and by running two instances of the module at 107 /// the same time (each of different `ModuleKind` version), allow users to 108 /// slowly migrate to a new one. This avoids complex and error-prone server-side 109 /// consensus-migration logic. 110 #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Encodable, Decodable)] 111 pub struct ModuleConsensusVersion { 112 pub major: u32, 113 pub minor: u32, 114 } 115 116 impl ModuleConsensusVersion { 117 pub const fn new(major: u32, minor: u32) -> Self { 118 Self { major, minor } 119 } 120 } 121 122 /// Api version supported by a core server or a client/server module at a given 123 /// [`ModuleConsensusVersion`]. 124 /// 125 /// Changing [`ModuleConsensusVersion`] implies resetting the api versioning. 126 /// 127 /// For a client and server to be able to communicate with each other: 128 /// 129 /// * The client needs API version support for the [`ModuleConsensusVersion`] 130 /// that the server is currently running with. 131 /// * Within that [`ModuleConsensusVersion`] during handshake negotiation 132 /// process client and server must find at least one `Api::major` version 133 /// where client's `minor` is lower or equal server's `major` version. 134 /// 135 /// A practical module implementation needs to implement large range of version 136 /// backward compatibility on both client and server side to accommodate end 137 /// user client devices receiving updates at a pace hard to control, and 138 /// technical and coordination challenges of upgrading servers. 139 #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Decodable, Encodable)] 140 pub struct ApiVersion { 141 /// Major API version 142 /// 143 /// Each time [`ModuleConsensusVersion`] is incremented, this number (and 144 /// `minor` number as well) should be reset to `0`. 145 /// 146 /// Should be incremented each time the API was changed in a 147 /// backward-incompatible ways (while resetting `minor` to `0`). 148 pub major: u32, 149 /// Minor API version 150 /// 151 /// * For clients this means *minimum* supported minor version of the 152 /// `major` version required by client implementation 153 /// * For servers this means *maximum* supported minor version of the 154 /// `major` version implemented by the server implementation 155 pub minor: u32, 156 } 157 158 impl ApiVersion { 159 pub const fn new(major: u32, minor: u32) -> Self { 160 Self { major, minor } 161 } 162 } 163 164 /// ``` 165 /// use fedimint_core::module::ApiVersion; 166 /// assert!(ApiVersion { major: 3, minor: 3 } < ApiVersion { major: 4, minor: 0 }); 167 /// assert!(ApiVersion { major: 3, minor: 3 } < ApiVersion { major: 3, minor: 5 }); 168 /// assert!(ApiVersion { major: 3, minor: 3 } == ApiVersion { major: 3, minor: 3 }); 169 /// ``` 170 impl cmp::PartialOrd for ApiVersion { 171 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { 172 Some(self.cmp(other)) 173 } 174 } 175 176 impl cmp::Ord for ApiVersion { 177 fn cmp(&self, other: &Self) -> cmp::Ordering { 178 self.major 179 .cmp(&other.major) 180 .then(self.minor.cmp(&other.minor)) 181 } 182 } 183 184 /// Multiple, disjoint, minimum required or maximum supported, [`ApiVersion`]s. 185 /// 186 /// If a given component can (potentially) support multiple different (distinct 187 /// major number), of an API, this type is used to express it. 188 /// 189 /// All [`ApiVersion`] values are in the context of the current consensus 190 /// version for the component in question. 191 /// 192 /// Each element must have a distinct major api number, and means 193 /// either minimum required API version of this major number (for the client), 194 /// or maximum supported version of this major number (for the server). 195 #[derive(Debug, Clone, Serialize, Default)] 196 pub struct MultiApiVersion(Vec<ApiVersion>); 197 198 impl MultiApiVersion { 199 pub fn new() -> Self { 200 Default::default() 201 } 202 203 /// Verify the invariant: sorted by unique major numbers 204 fn is_consistent(&self) -> bool { 205 self.0 206 .iter() 207 .fold((None, true), |(prev, is_sorted), next| { 208 ( 209 Some(*next), 210 is_sorted && prev.map(|prev| prev.major < next.major).unwrap_or(true), 211 ) 212 }) 213 .1 214 } 215 216 fn iter(&self) -> MultiApiVersionIter { 217 MultiApiVersionIter(self.0.iter()) 218 } 219 220 pub fn try_from_iter<T: IntoIterator<Item = ApiVersion>>( 221 iter: T, 222 ) -> result::Result<Self, ApiVersion> { 223 Result::from_iter(iter) 224 } 225 226 /// Insert `version` to the list of supported APIs 227 /// 228 /// Returns `Ok` if no existing element with the same `major` version was 229 /// found and new `version` was successfully inserted. Returns `Err` if 230 /// an existing element with the same `major` version was found, to allow 231 /// modifying its `minor` number. This is useful when merging required / 232 /// supported version sequences with each other. 233 fn try_insert(&mut self, version: ApiVersion) -> result::Result<(), &mut u32> { 234 let ret = match self 235 .0 236 .binary_search_by_key(&version.major, |version| version.major) 237 { 238 Ok(found_idx) => Err(self 239 .0 240 .get_mut(found_idx) 241 .map(|v| &mut v.minor) 242 .expect("element must exist - just checked")), 243 Err(insert_idx) => { 244 self.0.insert(insert_idx, version); 245 Ok(()) 246 } 247 }; 248 249 ret 250 } 251 252 pub(crate) fn get_by_major(&self, major: u32) -> Option<ApiVersion> { 253 self.0 254 .binary_search_by_key(&major, |version| version.major) 255 .ok() 256 .map(|index| { 257 self.0 258 .get(index) 259 .copied() 260 .expect("Must exist because binary_search_by_key told us so") 261 }) 262 } 263 } 264 265 impl<'de> Deserialize<'de> for MultiApiVersion { 266 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error> 267 where 268 D: serde::de::Deserializer<'de>, 269 { 270 use serde::de::Error; 271 272 let inner = Vec::<ApiVersion>::deserialize(deserializer)?; 273 274 let ret = Self(inner); 275 276 if !ret.is_consistent() { 277 return Err(D::Error::custom( 278 "Invalid MultiApiVersion value: inconsistent", 279 )); 280 } 281 282 Ok(ret) 283 } 284 } 285 286 pub struct MultiApiVersionIter<'a>(std::slice::Iter<'a, ApiVersion>); 287 288 impl<'a> Iterator for MultiApiVersionIter<'a> { 289 type Item = ApiVersion; 290 291 fn next(&mut self) -> Option<Self::Item> { 292 self.0.next().copied() 293 } 294 } 295 296 impl<'a> IntoIterator for &'a MultiApiVersion { 297 type Item = ApiVersion; 298 299 type IntoIter = MultiApiVersionIter<'a>; 300 301 fn into_iter(self) -> Self::IntoIter { 302 self.iter() 303 } 304 } 305 306 impl FromIterator<ApiVersion> for Result<MultiApiVersion, ApiVersion> { 307 fn from_iter<T: IntoIterator<Item = ApiVersion>>(iter: T) -> Self { 308 let mut s = MultiApiVersion::new(); 309 for version in iter.into_iter() { 310 if s.try_insert(version).is_err() { 311 return Err(version); 312 } 313 } 314 Ok(s) 315 } 316 } 317 318 #[test] 319 fn api_version_multi_sanity() { 320 let mut mav = MultiApiVersion::new(); 321 322 assert_eq!(mav.try_insert(ApiVersion { major: 2, minor: 3 }), Ok(())); 323 324 assert_eq!(mav.get_by_major(0), None); 325 assert_eq!(mav.get_by_major(2), Some(ApiVersion { major: 2, minor: 3 })); 326 327 assert_eq!( 328 mav.try_insert(ApiVersion { major: 2, minor: 1 }), 329 Err(&mut 3) 330 ); 331 *mav.try_insert(ApiVersion { major: 2, minor: 2 }) 332 .expect_err("must be error, just like one line above") += 1; 333 assert_eq!(mav.try_insert(ApiVersion { major: 1, minor: 2 }), Ok(())); 334 assert_eq!(mav.try_insert(ApiVersion { major: 3, minor: 4 }), Ok(())); 335 assert_eq!( 336 mav.try_insert(ApiVersion { major: 2, minor: 0 }), 337 Err(&mut 4) 338 ); 339 assert_eq!(mav.get_by_major(5), None); 340 assert_eq!(mav.get_by_major(3), Some(ApiVersion { major: 3, minor: 4 })); 341 342 debug_assert!(mav.is_consistent()); 343 } 344 345 #[test] 346 fn api_version_multi_from_iter_sanity() { 347 assert!(result::Result::<MultiApiVersion, ApiVersion>::from_iter([]).is_ok()); 348 assert!( 349 result::Result::<MultiApiVersion, ApiVersion>::from_iter([ApiVersion { 350 major: 0, 351 minor: 0 352 }]) 353 .is_ok() 354 ); 355 assert!(result::Result::<MultiApiVersion, ApiVersion>::from_iter([ 356 ApiVersion { major: 0, minor: 1 }, 357 ApiVersion { major: 1, minor: 2 } 358 ]) 359 .is_ok()); 360 assert!(result::Result::<MultiApiVersion, ApiVersion>::from_iter([ 361 ApiVersion { major: 0, minor: 1 }, 362 ApiVersion { major: 1, minor: 2 }, 363 ApiVersion { major: 0, minor: 1 }, 364 ]) 365 .is_err()); 366 } 367 368 #[derive(Debug, Clone, Serialize, Deserialize)] 369 pub struct SupportedCoreApiVersions { 370 pub core_consensus: CoreConsensusVersion, 371 /// Supported Api versions for this core consensus versions 372 pub api: MultiApiVersion, 373 } 374 375 impl SupportedCoreApiVersions { 376 /// Get minor supported version by consensus and major numbers 377 pub fn get_minor_api_version( 378 &self, 379 core_consensus: CoreConsensusVersion, 380 major: u32, 381 ) -> Option<u32> { 382 if self.core_consensus.major != core_consensus.major { 383 return None; 384 } 385 386 self.api.get_by_major(major).map(|v| { 387 debug_assert_eq!(v.major, major); 388 v.minor 389 }) 390 } 391 } 392 393 #[derive(Debug, Clone, Serialize, Deserialize)] 394 pub struct SupportedModuleApiVersions { 395 pub core_consensus: CoreConsensusVersion, 396 pub module_consensus: ModuleConsensusVersion, 397 /// Supported Api versions for this core & module consensus versions 398 pub api: MultiApiVersion, 399 } 400 401 impl SupportedModuleApiVersions { 402 /// Create `SupportedModuleApiVersions` from raw parts 403 /// 404 /// Panics if `api_version` parts conflict as per 405 /// [`SupportedModuleApiVersions`] invariants. 406 pub fn from_raw(core: (u32, u32), module: (u32, u32), api_versions: &[(u32, u32)]) -> Self { 407 Self { 408 core_consensus: CoreConsensusVersion::new(core.0, core.1), 409 module_consensus: ModuleConsensusVersion::new(module.0, module.1), 410 api: result::Result::<MultiApiVersion, ApiVersion>::from_iter( 411 api_versions 412 .iter() 413 .copied() 414 .map(|(major, minor)| ApiVersion { major, minor }), 415 ) 416 .expect( 417 "overlapping (conflicting) api versions when declaring SupportedModuleApiVersions", 418 ), 419 } 420 } 421 422 /// Get minor supported version by consensus and major numbers 423 pub fn get_minor_api_version( 424 &self, 425 core_consensus: CoreConsensusVersion, 426 module_consensus: ModuleConsensusVersion, 427 major: u32, 428 ) -> Option<u32> { 429 if self.core_consensus.major != core_consensus.major { 430 return None; 431 } 432 433 if self.module_consensus.major != module_consensus.major { 434 return None; 435 } 436 437 self.api.get_by_major(major).map(|v| { 438 debug_assert_eq!(v.major, major); 439 v.minor 440 }) 441 } 442 } 443 444 #[derive(Debug, Clone, Serialize, Deserialize)] 445 pub struct SupportedApiVersionsSummary { 446 pub core: SupportedCoreApiVersions, 447 pub modules: BTreeMap<ModuleInstanceId, SupportedModuleApiVersions>, 448 } 449 450 /// A summary of server API versions for core and all registered modules. 451 #[derive(Serialize)] 452 pub struct ServerApiVersionsSummary { 453 pub core: MultiApiVersion, 454 pub modules: BTreeMap<ModuleKind, MultiApiVersion>, 455 } 456 457 /// A summary of server database versions for all registered modules. 458 #[derive(Serialize)] 459 pub struct ServerDbVersionsSummary { 460 pub modules: BTreeMap<ModuleKind, DatabaseVersion>, 461 }