Add feature set identifier to gossiped version information

This commit is contained in:
Michael Vines
2020-09-21 19:54:43 -07:00
parent 93259f0bae
commit 35f5f9fc7b
8 changed files with 128 additions and 28 deletions

1
Cargo.lock generated
View File

@ -4685,6 +4685,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"solana-logger 1.4.0", "solana-logger 1.4.0",
"solana-runtime",
"solana-sdk 1.4.0", "solana-sdk 1.4.0",
"solana-sdk-macro-frozen-abi 1.4.0", "solana-sdk-macro-frozen-abi 1.4.0",
] ]

View File

@ -17,7 +17,7 @@ use solana_client::{
pubsub_client::PubsubClient, pubsub_client::PubsubClient,
rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient}, rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter}, rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter},
rpc_response::{RpcVersionInfo, SlotInfo}, rpc_response::SlotInfo,
}; };
use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{ use solana_sdk::{
@ -1314,12 +1314,9 @@ pub fn process_show_validators(
for contact_info in rpc_client.get_cluster_nodes()? { for contact_info in rpc_client.get_cluster_nodes()? {
node_version.insert( node_version.insert(
contact_info.pubkey, contact_info.pubkey,
RpcVersionInfo { contact_info
solana_core: contact_info .version
.version .unwrap_or_else(|| unknown_version.clone()),
.unwrap_or_else(|| unknown_version.clone()),
}
.to_string(),
); );
} }

View File

@ -121,6 +121,7 @@ pub enum ReceivedSignatureResult {
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcContactInfo { pub struct RpcContactInfo {
/// Pubkey of the node as a base-58 string /// Pubkey of the node as a base-58 string
pub pubkey: String, pub pubkey: String,
@ -132,6 +133,8 @@ pub struct RpcContactInfo {
pub rpc: Option<SocketAddr>, pub rpc: Option<SocketAddr>,
/// Software version /// Software version
pub version: Option<String>, pub version: Option<String>,
/// First 4 bytes of the FeatureSet identifier
pub feature_set: Option<u32>,
} }
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot /// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
@ -142,6 +145,8 @@ pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
pub struct RpcVersionInfo { pub struct RpcVersionInfo {
/// The current version of solana-core /// The current version of solana-core
pub solana_core: String, pub solana_core: String,
/// first 4 bytes of the FeatureSet identifier
pub feature_set: Option<u32>,
} }
impl fmt::Debug for RpcVersionInfo { impl fmt::Debug for RpcVersionInfo {

View File

@ -359,7 +359,7 @@ pub fn make_accounts_hashes_message(
} }
// TODO These messages should go through the gpu pipeline for spam filtering // TODO These messages should go through the gpu pipeline for spam filtering
#[frozen_abi(digest = "CnN1gW2K2TRydGc84eYnQJwdTADPjQf6LJLZ4RP1QeoH")] #[frozen_abi(digest = "3ZHQscZ9SgxKh45idzHv3hiagyyPRtDgeySmJn171PTi")]
#[derive(Serialize, Deserialize, Debug, AbiEnumVisitor, AbiExample)] #[derive(Serialize, Deserialize, Debug, AbiEnumVisitor, AbiExample)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
enum Protocol { enum Protocol {
@ -573,7 +573,7 @@ impl ClusterInfo {
} }
let ip_addr = node.gossip.ip(); let ip_addr = node.gossip.ip();
Some(format!( Some(format!(
"{:15} {:2}| {:5} | {:44} |{:^15}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n", "{:15} {:2}| {:5} | {:44} |{:^9}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n",
if ContactInfo::is_valid_address(&node.gossip) { if ContactInfo::is_valid_address(&node.gossip) {
ip_addr.to_string() ip_addr.to_string()
} else { } else {
@ -605,8 +605,8 @@ impl ClusterInfo {
format!( format!(
"IP Address |Age(ms)| Node identifier \ "IP Address |Age(ms)| Node identifier \
| Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\ | Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\
------------------+-------+----------------------------------------------+---------------+\ ------------------+-------+----------------------------------------------+---------+\
------+------+------+------+------+------+------+------+------+--------\n\ ------+------+------+------+------+------+------+------+------+--------\n\
{}\ {}\
Nodes: {}{}{}", Nodes: {}{}{}",
@ -894,7 +894,8 @@ impl ClusterInfo {
} }
pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> { pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> {
self.gossip let version = self
.gossip
.read() .read()
.unwrap() .unwrap()
.crds .crds
@ -902,7 +903,21 @@ impl ClusterInfo {
.get(&CrdsValueLabel::Version(*pubkey)) .get(&CrdsValueLabel::Version(*pubkey))
.map(|x| x.value.version()) .map(|x| x.value.version())
.flatten() .flatten()
.map(|version| version.version.clone()) .map(|version| version.version.clone());
if version.is_none() {
self.gossip
.read()
.unwrap()
.crds
.table
.get(&CrdsValueLabel::LegacyVersion(*pubkey))
.map(|x| x.value.legacy_version())
.flatten()
.map(|version| version.version.clone().into())
} else {
version
}
} }
/// all validators that have a valid rpc port regardless of `shred_version`. /// all validators that have a valid rpc port regardless of `shred_version`.

View File

@ -75,6 +75,7 @@ pub enum CrdsData {
SnapshotHashes(SnapshotHash), SnapshotHashes(SnapshotHash),
AccountsHashes(SnapshotHash), AccountsHashes(SnapshotHash),
EpochSlots(EpochSlotsIndex, EpochSlots), EpochSlots(EpochSlotsIndex, EpochSlots),
LegacyVersion(LegacyVersion),
Version(Version), Version(Version),
} }
@ -102,6 +103,7 @@ impl Sanitize for CrdsData {
} }
val.sanitize() val.sanitize()
} }
CrdsData::LegacyVersion(version) => version.sanitize(),
CrdsData::Version(version) => version.sanitize(), CrdsData::Version(version) => version.sanitize(),
} }
} }
@ -208,6 +210,23 @@ impl Vote {
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct LegacyVersion {
pub from: Pubkey,
pub wallclock: u64,
pub version: solana_version::LegacyVersion,
}
impl Sanitize for LegacyVersion {
fn sanitize(&self) -> Result<(), SanitizeError> {
if self.wallclock >= MAX_WALLCLOCK {
return Err(SanitizeError::ValueOutOfBounds);
}
self.from.sanitize()?;
self.version.sanitize()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct Version { pub struct Version {
pub from: Pubkey, pub from: Pubkey,
@ -245,6 +264,7 @@ pub enum CrdsValueLabel {
SnapshotHashes(Pubkey), SnapshotHashes(Pubkey),
EpochSlots(EpochSlotsIndex, Pubkey), EpochSlots(EpochSlotsIndex, Pubkey),
AccountsHashes(Pubkey), AccountsHashes(Pubkey),
LegacyVersion(Pubkey),
Version(Pubkey), Version(Pubkey),
} }
@ -257,6 +277,7 @@ impl fmt::Display for CrdsValueLabel {
CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHash({})", self.pubkey()), CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHash({})", self.pubkey()),
CrdsValueLabel::EpochSlots(ix, _) => write!(f, "EpochSlots({}, {})", ix, self.pubkey()), CrdsValueLabel::EpochSlots(ix, _) => write!(f, "EpochSlots({}, {})", ix, self.pubkey()),
CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()), CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()),
CrdsValueLabel::LegacyVersion(_) => write!(f, "LegacyVersion({})", self.pubkey()),
CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()), CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()),
} }
} }
@ -271,6 +292,7 @@ impl CrdsValueLabel {
CrdsValueLabel::SnapshotHashes(p) => *p, CrdsValueLabel::SnapshotHashes(p) => *p,
CrdsValueLabel::EpochSlots(_, p) => *p, CrdsValueLabel::EpochSlots(_, p) => *p,
CrdsValueLabel::AccountsHashes(p) => *p, CrdsValueLabel::AccountsHashes(p) => *p,
CrdsValueLabel::LegacyVersion(p) => *p,
CrdsValueLabel::Version(p) => *p, CrdsValueLabel::Version(p) => *p,
} }
} }
@ -300,6 +322,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(hash) => hash.wallclock, CrdsData::SnapshotHashes(hash) => hash.wallclock,
CrdsData::AccountsHashes(hash) => hash.wallclock, CrdsData::AccountsHashes(hash) => hash.wallclock,
CrdsData::EpochSlots(_, p) => p.wallclock, CrdsData::EpochSlots(_, p) => p.wallclock,
CrdsData::LegacyVersion(version) => version.wallclock,
CrdsData::Version(version) => version.wallclock, CrdsData::Version(version) => version.wallclock,
} }
} }
@ -311,6 +334,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(hash) => hash.from, CrdsData::SnapshotHashes(hash) => hash.from,
CrdsData::AccountsHashes(hash) => hash.from, CrdsData::AccountsHashes(hash) => hash.from,
CrdsData::EpochSlots(_, p) => p.from, CrdsData::EpochSlots(_, p) => p.from,
CrdsData::LegacyVersion(version) => version.from,
CrdsData::Version(version) => version.from, CrdsData::Version(version) => version.from,
} }
} }
@ -322,6 +346,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()), CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()),
CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()), CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()),
CrdsData::EpochSlots(ix, _) => CrdsValueLabel::EpochSlots(*ix, self.pubkey()), CrdsData::EpochSlots(ix, _) => CrdsValueLabel::EpochSlots(*ix, self.pubkey()),
CrdsData::LegacyVersion(_) => CrdsValueLabel::LegacyVersion(self.pubkey()),
CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()), CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()),
} }
} }
@ -373,6 +398,13 @@ impl CrdsValue {
} }
} }
pub fn legacy_version(&self) -> Option<&LegacyVersion> {
match &self.data {
CrdsData::LegacyVersion(legacy_version) => Some(legacy_version),
_ => None,
}
}
pub fn version(&self) -> Option<&Version> { pub fn version(&self) -> Option<&Version> {
match &self.data { match &self.data {
CrdsData::Version(version) => Some(version), CrdsData::Version(version) => Some(version),
@ -387,6 +419,7 @@ impl CrdsValue {
CrdsValueLabel::LowestSlot(*key), CrdsValueLabel::LowestSlot(*key),
CrdsValueLabel::SnapshotHashes(*key), CrdsValueLabel::SnapshotHashes(*key),
CrdsValueLabel::AccountsHashes(*key), CrdsValueLabel::AccountsHashes(*key),
CrdsValueLabel::LegacyVersion(*key),
CrdsValueLabel::Version(*key), CrdsValueLabel::Version(*key),
]; ];
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key))); labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
@ -438,7 +471,7 @@ mod test {
#[test] #[test]
fn test_labels() { fn test_labels() {
let mut hits = [false; 5 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize]; let mut hits = [false; 6 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize];
// this method should cover all the possible labels // this method should cover all the possible labels
for v in &CrdsValue::record_labels(&Pubkey::default()) { for v in &CrdsValue::record_labels(&Pubkey::default()) {
match v { match v {
@ -446,10 +479,11 @@ mod test {
CrdsValueLabel::LowestSlot(_) => hits[1] = true, CrdsValueLabel::LowestSlot(_) => hits[1] = true,
CrdsValueLabel::SnapshotHashes(_) => hits[2] = true, CrdsValueLabel::SnapshotHashes(_) => hits[2] = true,
CrdsValueLabel::AccountsHashes(_) => hits[3] = true, CrdsValueLabel::AccountsHashes(_) => hits[3] = true,
CrdsValueLabel::Version(_) => hits[4] = true, CrdsValueLabel::LegacyVersion(_) => hits[4] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 5] = true, CrdsValueLabel::Version(_) => hits[5] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 6] = true,
CrdsValueLabel::EpochSlots(ix, _) => { CrdsValueLabel::EpochSlots(ix, _) => {
hits[*ix as usize + MAX_VOTES as usize + 5] = true hits[*ix as usize + MAX_VOTES as usize + 6] = true
} }
} }
} }

View File

@ -1945,14 +1945,19 @@ impl RpcSol for RpcSolImpl {
if my_shred_version == contact_info.shred_version if my_shred_version == contact_info.shred_version
&& ContactInfo::is_valid_address(&contact_info.gossip) && ContactInfo::is_valid_address(&contact_info.gossip)
{ {
let (version, feature_set) =
if let Some(version) = cluster_info.get_node_version(&contact_info.id) {
(Some(version.to_string()), Some(version.feature_set))
} else {
(None, None)
};
Some(RpcContactInfo { Some(RpcContactInfo {
pubkey: contact_info.id.to_string(), pubkey: contact_info.id.to_string(),
gossip: Some(contact_info.gossip), gossip: Some(contact_info.gossip),
tpu: valid_address_or_none(&contact_info.tpu), tpu: valid_address_or_none(&contact_info.tpu),
rpc: valid_address_or_none(&contact_info.rpc), rpc: valid_address_or_none(&contact_info.rpc),
version: cluster_info version,
.get_node_version(&contact_info.id) feature_set,
.map(|v| v.to_string()),
}) })
} else { } else {
None // Exclude spy nodes None // Exclude spy nodes
@ -2299,8 +2304,10 @@ impl RpcSol for RpcSolImpl {
fn get_version(&self, _: Self::Metadata) -> Result<RpcVersionInfo> { fn get_version(&self, _: Self::Metadata) -> Result<RpcVersionInfo> {
debug!("get_version rpc request received"); debug!("get_version rpc request received");
let version = solana_version::Version::default();
Ok(RpcVersionInfo { Ok(RpcVersionInfo {
solana_core: solana_version::Version::default().to_string(), solana_core: version.to_string(),
feature_set: Some(version.feature_set),
}) })
} }
@ -2843,7 +2850,7 @@ pub mod tests {
.expect("actual response deserialization"); .expect("actual response deserialization");
let expected = format!( let expected = format!(
r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null}}],"id":1}}"#, r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null, "featureSet": null}}],"id":1}}"#,
leader_pubkey, leader_pubkey,
rpc_port::DEFAULT_RPC_PORT rpc_port::DEFAULT_RPC_PORT
); );
@ -4394,10 +4401,12 @@ pub mod tests {
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getVersion"}"#; let req = r#"{"jsonrpc":"2.0","id":1,"method":"getVersion"}"#;
let res = io.handle_request_sync(&req, meta); let res = io.handle_request_sync(&req, meta);
let version = solana_version::Version::default();
let expected = json!({ let expected = json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"result": { "result": {
"solana-core": solana_version::version!().to_string() "solana-core": version.to_string(),
"feature-set": version.feature_set,
}, },
"id": 1 "id": 1
}); });

View File

@ -14,6 +14,7 @@ serde = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
solana-logger = { path = "../logger", version = "1.4.0" } solana-logger = { path = "../logger", version = "1.4.0" }
solana-sdk = { path = "../sdk", version = "1.4.0" } solana-sdk = { path = "../sdk", version = "1.4.0" }
solana-runtime = { path = "../runtime", version = "1.4.0" }
solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.4.0" } solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.4.0" }
[lib] [lib]

View File

@ -3,19 +3,42 @@
extern crate serde_derive; extern crate serde_derive;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use solana_sdk::sanitize::Sanitize; use solana_sdk::sanitize::Sanitize;
use std::fmt; use std::{convert::TryInto, fmt};
#[macro_use] #[macro_use]
extern crate solana_sdk_macro_frozen_abi; extern crate solana_sdk_macro_frozen_abi;
// Older version structure used by 1.3.12 and earlier releases
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct Version { pub struct LegacyVersion {
major: u16, major: u16,
minor: u16, minor: u16,
patch: u16, patch: u16,
commit: Option<u32>, // first 4 bytes of the sha1 commit hash commit: Option<u32>, // first 4 bytes of the sha1 commit hash
} }
impl Sanitize for LegacyVersion {}
#[derive(Serialize, Deserialize, Clone, PartialEq, AbiExample)]
pub struct Version {
pub major: u16,
pub minor: u16,
pub patch: u16,
pub commit: Option<u32>, // first 4 bytes of the sha1 commit hash
pub feature_set: u32, // first 4 bytes of the FeatureSet identifier
}
impl From<LegacyVersion> for Version {
fn from(legacy_version: LegacyVersion) -> Self {
Self {
major: legacy_version.major,
minor: legacy_version.minor,
patch: legacy_version.patch,
commit: legacy_version.commit,
feature_set: 0,
}
}
}
fn compute_commit(sha1: Option<&'static str>) -> Option<u32> { fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
let sha1 = sha1?; let sha1 = sha1?;
if sha1.len() < 8 { if sha1.len() < 8 {
@ -27,27 +50,42 @@ fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
impl Default for Version { impl Default for Version {
fn default() -> Self { fn default() -> Self {
let feature_set = u32::from_le_bytes(
solana_runtime::feature_set::FeatureSet::default()
.id
.as_ref()[..4]
.try_into()
.unwrap(),
);
Self { Self {
major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
commit: compute_commit(option_env!("CI_COMMIT")), commit: compute_commit(option_env!("CI_COMMIT")),
feature_set,
} }
} }
} }
impl fmt::Display for Version { 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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"{}.{}.{} {}", "{}.{}.{} (src:{}; feat:{})",
self.major, self.major,
self.minor, self.minor,
self.patch, self.patch,
match self.commit { match self.commit {
None => "devbuild".to_string(), None => "devbuild".to_string(),
Some(commit) => format!("{:08x}", commit), Some(commit) => format!("{:08x}", commit),
} },
self.feature_set,
) )
} }
} }