Skip to main content

solana_version/
lib.rs

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