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 Default for Version {
57 fn default() -> Self {
58 let feature_set =
59 u32::from_le_bytes(solana_feature_set::ID.as_ref()[..4].try_into().unwrap());
60 Self {
61 major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
62 minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
63 patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
64 commit: compute_commit(option_env!("CI_COMMIT")).unwrap_or_default(),
65 feature_set,
66 client: u16::try_from(ClientId::Agave).unwrap(),
68 }
69 }
70}
71
72impl fmt::Display for Version {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(f, "{}.{}.{}", self.major, self.minor, self.patch,)
75 }
76}
77
78impl fmt::Debug for Version {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(
81 f,
82 "{}.{}.{} (src:{:08x}; feat:{}, client:{:?})",
83 self.major,
84 self.minor,
85 self.patch,
86 self.commit,
87 self.feature_set,
88 self.client(),
89 )
90 }
91}
92
93impl Sanitize for Version {}
94
95impl From<u16> for ClientId {
96 fn from(client: u16) -> Self {
97 match client {
98 0u16 => Self::SolanaLabs,
99 1u16 => Self::JitoLabs,
100 2u16 => Self::Firedancer,
101 3u16 => Self::Agave,
102 _ => Self::Unknown(client),
103 }
104 }
105}
106
107impl TryFrom<ClientId> for u16 {
108 type Error = String;
109
110 fn try_from(client: ClientId) -> Result<Self, Self::Error> {
111 match client {
112 ClientId::SolanaLabs => Ok(0u16),
113 ClientId::JitoLabs => Ok(1u16),
114 ClientId::Firedancer => Ok(2u16),
115 ClientId::Agave => Ok(3u16),
116 ClientId::Unknown(client @ 0u16..=3u16) => Err(format!("Invalid client: {client}")),
117 ClientId::Unknown(client) => Ok(client),
118 }
119 }
120}
121
122#[macro_export]
123macro_rules! semver {
124 () => {
125 &*format!("{}", $crate::Version::default())
126 };
127}
128
129#[macro_export]
130macro_rules! version {
131 () => {
132 &*format!("{:?}", $crate::Version::default())
133 };
134}
135
136#[cfg(test)]
137mod test {
138 use super::*;
139
140 #[test]
141 fn test_compute_commit() {
142 assert_eq!(compute_commit(None), None);
143 assert_eq!(compute_commit(Some("1234567890")), Some(0x1234_5678));
144 assert_eq!(compute_commit(Some("HEAD")), None);
145 assert_eq!(compute_commit(Some("garbagein")), None);
146 }
147
148 #[test]
149 fn test_client_id() {
150 assert_eq!(ClientId::from(0u16), ClientId::SolanaLabs);
151 assert_eq!(ClientId::from(1u16), ClientId::JitoLabs);
152 assert_eq!(ClientId::from(2u16), ClientId::Firedancer);
153 assert_eq!(ClientId::from(3u16), ClientId::Agave);
154 for client in 4u16..=u16::MAX {
155 assert_eq!(ClientId::from(client), ClientId::Unknown(client));
156 }
157 assert_eq!(u16::try_from(ClientId::SolanaLabs), Ok(0u16));
158 assert_eq!(u16::try_from(ClientId::JitoLabs), Ok(1u16));
159 assert_eq!(u16::try_from(ClientId::Firedancer), Ok(2u16));
160 assert_eq!(u16::try_from(ClientId::Agave), Ok(3u16));
161 for client in 0..=3u16 {
162 assert_eq!(
163 u16::try_from(ClientId::Unknown(client)),
164 Err(format!("Invalid client: {client}"))
165 );
166 }
167 for client in 4u16..=u16::MAX {
168 assert_eq!(u16::try_from(ClientId::Unknown(client)), Ok(client));
169 }
170 }
171}