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