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