solana_version/
lib.rs

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