iota_package_management/
system_package_versions.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeMap, sync::LazyLock};
6
7use anyhow::Context;
8use iota_protocol_config::ProtocolVersion;
9
10/// Static mapping from protocol versions to the metadata for the system
11/// packages.
12// Generated by [generate_system_packages_version_table] in build.rs
13static VERSION_TABLE: LazyLock<BTreeMap<ProtocolVersion, SystemPackagesVersion>> =
14    LazyLock::new(|| {
15        BTreeMap::from(include!(concat!(
16            env!("OUT_DIR"),
17            "/system_packages_version_table.rs"
18        )))
19    });
20
21#[derive(Debug)]
22pub struct SystemPackagesVersion {
23    pub git_revision: String,
24    pub packages: Vec<SystemPackage>,
25}
26
27#[derive(Debug)]
28pub struct SystemPackage {
29    /// The name of the package, e.g. "Iota".
30    pub package_name: String,
31
32    /// The path to the package in the iota monorepo
33    /// e.g. "crates/iota-framework/packages/iota-framework".
34    pub repo_path: String,
35}
36
37impl PartialEq for SystemPackagesVersion {
38    fn eq(&self, other: &Self) -> bool {
39        self.git_revision == other.git_revision
40    }
41}
42
43/// Return the system packages snapshot for the latest known protocol version
44pub fn latest_system_packages() -> &'static SystemPackagesVersion {
45    VERSION_TABLE
46        .last_key_value()
47        .expect("known system package version table should be nonempty")
48        .1
49}
50
51/// Return the latest protocol version that is not newer than the requested
52/// `version` (or `Err` if there is no such version).
53///
54/// The returned [ProtocolVersion] is the protocol version that introduced the
55/// returned [SystemPackagesVersion]; this may be older than the requested
56/// `version` if either:
57/// 1. the system packages did not change when `version` was released, or
58/// 2. this binary is older than the requested version and therefore doesn't
59///    know about the latest version of the system packages
60///
61/// You can distinguish these cases by comparing `version` with
62/// [ProtocolVersion::MAX].
63pub fn system_packages_for_protocol(
64    version: ProtocolVersion,
65) -> anyhow::Result<(&'static SystemPackagesVersion, ProtocolVersion)> {
66    let (protocol, system_packages) = VERSION_TABLE
67        .range(..=version)
68        .next_back()
69        .context(format!("Unrecognized protocol version {version:?}"))?;
70    Ok((system_packages, *protocol))
71}
72
73#[test]
74/// There is at least one known version of the system packages.
75fn test_nonempty_version_table() {
76    assert!(!VERSION_TABLE.is_empty());
77}
78
79#[test]
80/// The hash for a specific version that we have one for is correctly returned.
81fn test_exact_version() {
82    let (system_packages, protocol) = system_packages_for_protocol(4.into()).unwrap();
83    assert_eq!(system_packages.git_revision, "49d5d7d99313");
84    assert_eq!(protocol, 4.into());
85    assert!(
86        system_packages
87            .packages
88            .iter()
89            .any(|p| p.package_name == "Iota")
90    );
91}
92
93#[test]
94#[ignore = "this test is not applicable, as we do not have version gaps yet."]
95/// We get the right hash for a version that we don't have an exact entry for.
96/// TODO: enable once a gap version is created.
97fn test_gap_version() {
98    // If versions 4 is missing in the manifest; version 3 should be returned.
99    assert_eq!(
100        system_packages_for_protocol(4.into()).unwrap(),
101        system_packages_for_protocol(3.into()).unwrap(),
102    );
103    // Version 5 is present though!
104    assert_ne!(
105        system_packages_for_protocol(5.into()).unwrap(),
106        system_packages_for_protocol(3.into()).unwrap(),
107    );
108}
109
110#[test]
111/// We get the correct hash for the latest known protocol version.
112fn test_version_latest() {
113    assert_eq!(
114        system_packages_for_protocol(ProtocolVersion::MAX)
115            .unwrap()
116            .0,
117        latest_system_packages()
118    );
119
120    assert_eq!(
121        system_packages_for_protocol(ProtocolVersion::MAX + 1)
122            .unwrap()
123            .0,
124        latest_system_packages()
125    )
126}
127
128#[test]
129/// We get an error if the protocol version is too small or too large.
130fn test_version_errors() {
131    assert!(system_packages_for_protocol(0.into()).is_err());
132}