#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
extern crate serde_derive;
pub use self::legacy::{LegacyVersion1, LegacyVersion2};
use {
serde_derive::{Deserialize, Serialize},
solana_sanitize::Sanitize,
solana_serde_varint as serde_varint,
std::{convert::TryInto, fmt},
};
#[cfg_attr(feature = "frozen-abi", macro_use)]
#[cfg(feature = "frozen-abi")]
extern crate solana_frozen_abi_macro;
mod legacy;
#[derive(Debug, Eq, PartialEq)]
enum ClientId {
SolanaLabs,
JitoLabs,
Firedancer,
Agave,
Unknown(u16),
}
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Version {
#[serde(with = "serde_varint")]
pub major: u16,
#[serde(with = "serde_varint")]
pub minor: u16,
#[serde(with = "serde_varint")]
pub patch: u16,
pub commit: u32, pub feature_set: u32, #[serde(with = "serde_varint")]
client: u16,
}
impl Version {
pub fn as_semver_version(&self) -> semver::Version {
semver::Version::new(self.major as u64, self.minor as u64, self.patch as u64)
}
fn client(&self) -> ClientId {
ClientId::from(self.client)
}
}
fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
u32::from_str_radix(sha1?.get(..8)?, 16).ok()
}
impl From<LegacyVersion2> for Version {
fn from(version: LegacyVersion2) -> Self {
Self {
major: version.major,
minor: version.minor,
patch: version.patch,
commit: version.commit.unwrap_or_default(),
feature_set: version.feature_set,
client: Version::default().client,
}
}
}
impl Default for Version {
fn default() -> Self {
let feature_set =
u32::from_le_bytes(solana_feature_set::ID.as_ref()[..4].try_into().unwrap());
Self {
major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
commit: compute_commit(option_env!("CI_COMMIT")).unwrap_or_default(),
feature_set,
client: u16::try_from(ClientId::Agave).unwrap(),
}
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch,)
}
}
impl fmt::Debug for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}.{} (src:{:08x}; feat:{}, client:{:?})",
self.major,
self.minor,
self.patch,
self.commit,
self.feature_set,
self.client(),
)
}
}
impl Sanitize for Version {}
impl From<u16> for ClientId {
fn from(client: u16) -> Self {
match client {
0u16 => Self::SolanaLabs,
1u16 => Self::JitoLabs,
2u16 => Self::Firedancer,
3u16 => Self::Agave,
_ => Self::Unknown(client),
}
}
}
impl TryFrom<ClientId> for u16 {
type Error = String;
fn try_from(client: ClientId) -> Result<Self, Self::Error> {
match client {
ClientId::SolanaLabs => Ok(0u16),
ClientId::JitoLabs => Ok(1u16),
ClientId::Firedancer => Ok(2u16),
ClientId::Agave => Ok(3u16),
ClientId::Unknown(client @ 0u16..=3u16) => Err(format!("Invalid client: {client}")),
ClientId::Unknown(client) => Ok(client),
}
}
}
#[macro_export]
macro_rules! semver {
() => {
&*format!("{}", $crate::Version::default())
};
}
#[macro_export]
macro_rules! version {
() => {
&*format!("{:?}", $crate::Version::default())
};
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_compute_commit() {
assert_eq!(compute_commit(None), None);
assert_eq!(compute_commit(Some("1234567890")), Some(0x1234_5678));
assert_eq!(compute_commit(Some("HEAD")), None);
assert_eq!(compute_commit(Some("garbagein")), None);
}
#[test]
fn test_client_id() {
assert_eq!(ClientId::from(0u16), ClientId::SolanaLabs);
assert_eq!(ClientId::from(1u16), ClientId::JitoLabs);
assert_eq!(ClientId::from(2u16), ClientId::Firedancer);
assert_eq!(ClientId::from(3u16), ClientId::Agave);
for client in 4u16..=u16::MAX {
assert_eq!(ClientId::from(client), ClientId::Unknown(client));
}
assert_eq!(u16::try_from(ClientId::SolanaLabs), Ok(0u16));
assert_eq!(u16::try_from(ClientId::JitoLabs), Ok(1u16));
assert_eq!(u16::try_from(ClientId::Firedancer), Ok(2u16));
assert_eq!(u16::try_from(ClientId::Agave), Ok(3u16));
for client in 0..=3u16 {
assert_eq!(
u16::try_from(ClientId::Unknown(client)),
Err(format!("Invalid client: {client}"))
);
}
for client in 4u16..=u16::MAX {
assert_eq!(u16::try_from(ClientId::Unknown(client)), Ok(client));
}
}
}