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 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, pub feature_set: u32, #[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)?, 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 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}