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