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