miraland_version/
lib.rs

1#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
2
3extern crate serde_derive;
4pub use self::legacy::{LegacyVersion1, LegacyVersion2};
5use {
6    serde_derive::{Deserialize, Serialize},
7    miraland_sdk::{sanitize::Sanitize, serde_varint},
8    std::{convert::TryInto, fmt},
9};
10#[macro_use]
11extern crate miraland_frozen_abi_macro;
12
13mod legacy;
14
15#[derive(Debug, Eq, PartialEq)]
16enum ClientId {
17    SolanaLabs,
18    JitoLabs,
19    Firedancer,
20    Miralander,
21    // If new variants are added, update From<u16> and TryFrom<ClientId>.
22    Unknown(u16),
23}
24
25#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, AbiExample)]
26pub struct Version {
27    #[serde(with = "serde_varint")]
28    pub major: u16,
29    #[serde(with = "serde_varint")]
30    pub minor: u16,
31    #[serde(with = "serde_varint")]
32    pub patch: u16,
33    pub commit: u32,      // first 4 bytes of the sha1 commit hash
34    pub feature_set: u32, // first 4 bytes of the FeatureSet identifier
35    #[serde(with = "serde_varint")]
36    client: u16,
37}
38
39impl Version {
40    pub fn as_semver_version(&self) -> semver::Version {
41        semver::Version::new(self.major as u64, self.minor as u64, self.patch as u64)
42    }
43
44    fn client(&self) -> ClientId {
45        ClientId::from(self.client)
46    }
47}
48
49fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
50    u32::from_str_radix(sha1?.get(..8)?, /*radix:*/ 16).ok()
51}
52
53impl Default for Version {
54    fn default() -> Self {
55        let feature_set = u32::from_le_bytes(
56            miraland_sdk::feature_set::ID.as_ref()[..4]
57                .try_into()
58                .unwrap(),
59        );
60        Self {
61            major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
62            minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
63            patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
64            commit: compute_commit(option_env!("CI_COMMIT")).unwrap_or_default(),
65            feature_set,
66            // Other client implementations need to modify this line.
67            // client: u16::try_from(ClientId::SolanaLabs).unwrap(), // MI
68            client: u16::try_from(ClientId::Miralander).unwrap(),
69        }
70    }
71}
72
73impl fmt::Display for Version {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{}.{}.{}", self.major, self.minor, self.patch,)
76    }
77}
78
79impl fmt::Debug for Version {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(
82            f,
83            "{}.{}.{} (src:{:08x}; feat:{}, client:{:?})",
84            self.major,
85            self.minor,
86            self.patch,
87            self.commit,
88            self.feature_set,
89            self.client(),
90        )
91    }
92}
93
94impl Sanitize for Version {}
95
96impl From<u16> for ClientId {
97    fn from(client: u16) -> Self {
98        match client {
99            0u16 => Self::SolanaLabs,
100            1u16 => Self::JitoLabs,
101            2u16 => Self::Firedancer,
102            3u16 => Self::Miralander,
103            _ => Self::Unknown(client),
104        }
105    }
106}
107
108impl TryFrom<ClientId> for u16 {
109    type Error = String;
110
111    fn try_from(client: ClientId) -> Result<Self, Self::Error> {
112        match client {
113            ClientId::SolanaLabs => Ok(0u16),
114            ClientId::JitoLabs => Ok(1u16),
115            ClientId::Firedancer => Ok(2u16),
116            ClientId::Miralander => Ok(3u16),
117            ClientId::Unknown(client @ 0u16..=2u16) => Err(format!("Invalid client: {client}")),
118            ClientId::Unknown(client) => Ok(client),
119        }
120    }
121}
122
123#[macro_export]
124macro_rules! semver {
125    () => {
126        &*format!("{}", $crate::Version::default())
127    };
128}
129
130#[macro_export]
131macro_rules! version {
132    () => {
133        &*format!("{:?}", $crate::Version::default())
134    };
135}
136
137#[cfg(test)]
138mod test {
139    use super::*;
140
141    #[test]
142    fn test_compute_commit() {
143        assert_eq!(compute_commit(None), None);
144        assert_eq!(compute_commit(Some("1234567890")), Some(0x1234_5678));
145        assert_eq!(compute_commit(Some("HEAD")), None);
146        assert_eq!(compute_commit(Some("garbagein")), None);
147    }
148
149    #[test]
150    fn test_client_id() {
151        assert_eq!(ClientId::from(0u16), ClientId::SolanaLabs);
152        assert_eq!(ClientId::from(1u16), ClientId::JitoLabs);
153        assert_eq!(ClientId::from(2u16), ClientId::Firedancer);
154        assert_eq!(ClientId::from(3u16), ClientId::Miralander);
155        for client in 3u16..=u16::MAX {
156            assert_eq!(ClientId::from(client), ClientId::Unknown(client));
157        }
158        assert_eq!(u16::try_from(ClientId::SolanaLabs), Ok(0u16));
159        assert_eq!(u16::try_from(ClientId::JitoLabs), Ok(1u16));
160        assert_eq!(u16::try_from(ClientId::Firedancer), Ok(2u16));
161        assert_eq!(u16::try_from(ClientId::Miralander), Ok(3u16));
162        for client in 0..=2u16 {
163            assert_eq!(
164                u16::try_from(ClientId::Unknown(client)),
165                Err(format!("Invalid client: {client}"))
166            );
167        }
168        for client in 3u16..=u16::MAX {
169            assert_eq!(u16::try_from(ClientId::Unknown(client)), Ok(client));
170        }
171    }
172}