From 8d7e90e9b8c328edfca067bdb919090e23cfcc46 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Mon, 11 May 2020 17:45:19 -0700 Subject: [PATCH] Advertise node version in gossip (#9986) automerge --- Cargo.lock | 18 ++++++++++---- Cargo.toml | 1 + core/Cargo.toml | 1 + core/src/cluster_info.rs | 37 +++++++++++++++++++++------- core/src/crds_value.rs | 52 ++++++++++++++++++++++++++++++++++++---- core/src/rpc.rs | 4 ++-- core/tests/client.rs | 2 +- version/.gitignore | 2 ++ version/Cargo.toml | 20 ++++++++++++++++ version/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++ 10 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 version/.gitignore create mode 100644 version/Cargo.toml create mode 100644 version/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1ead047115..12e97f085d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2711,9 +2711,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" dependencies = [ "serde_derive", ] @@ -2729,9 +2729,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2 1.0.12", "quote 1.0.1", @@ -3290,6 +3290,7 @@ dependencies = [ "solana-streamer", "solana-sys-tuner", "solana-transaction-status", + "solana-version", "solana-vote-program", "solana-vote-signer", "systemstat", @@ -4007,6 +4008,15 @@ dependencies = [ "solana-vote-signer", ] +[[package]] +name = "solana-version" +version = "1.1.11" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk", +] + [[package]] name = "solana-vest-program" version = "1.1.11" diff --git a/Cargo.toml b/Cargo.toml index 57f0164520..504e53929f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "transaction-status", "upload-perf", "net-utils", + "version", "vote-signer", "cli", "rayon-threadlimit", diff --git a/core/Cargo.toml b/core/Cargo.toml index 9d76c2016d..715b1b2eca 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -61,6 +61,7 @@ solana-sdk = { path = "../sdk", version = "1.1.11" } solana-stake-program = { path = "../programs/stake", version = "1.1.11" } solana-storage-program = { path = "../programs/storage", version = "1.1.11" } solana-streamer = { path = "../streamer", version = "1.1.11" } +solana-version = { path = "../version", version = "1.1.11" } solana-vote-program = { path = "../programs/vote", version = "1.1.11" } solana-vote-signer = { path = "../vote-signer", version = "1.1.11" } solana-sys-tuner = { path = "../sys-tuner", version = "1.1.11" } diff --git a/core/src/cluster_info.rs b/core/src/cluster_info.rs index 20ca23881d..e86a1615c0 100644 --- a/core/src/cluster_info.rs +++ b/core/src/cluster_info.rs @@ -18,8 +18,8 @@ use crate::{ crds_gossip_error::CrdsGossipError, crds_gossip_pull::{CrdsFilter, CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS}, crds_value::{ - self, CrdsData, CrdsValue, CrdsValueLabel, EpochSlotsIndex, LowestSlot, SnapshotHash, Vote, - MAX_WALLCLOCK, + self, CrdsData, CrdsValue, CrdsValueLabel, EpochSlotsIndex, LowestSlot, SnapshotHash, + Version, Vote, MAX_WALLCLOCK, }, epoch_slots::EpochSlots, result::{Error, Result}, @@ -378,6 +378,7 @@ impl ClusterInfo { archivers += 1; } + let node_version = self.get_node_version(&node.id); if my_shred_version != 0 && (node.shred_version != 0 && node.shred_version != my_shred_version) { different_shred_nodes += 1; None @@ -393,10 +394,9 @@ impl ClusterInfo { "none".to_string() } } - let ip_addr = node.gossip.ip(); Some(format!( - "{:15} {:2}| {:5} | {:44} | {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n", + "{:15} {:2}| {:5} | {:44} |{:^15}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n", if ContactInfo::is_valid_address(&node.gossip) { ip_addr.to_string() } else { @@ -405,6 +405,11 @@ impl ClusterInfo { if node.id == my_pubkey { "me" } else { "" }.to_string(), now.saturating_sub(last_updated), node.id.to_string(), + if let Some(node_version) = node_version { + node_version.to_string() + } else { + "-".to_string() + }, addr_to_string(&ip_addr, &node.gossip), addr_to_string(&ip_addr, &node.tpu), addr_to_string(&ip_addr, &node.tpu_forwards), @@ -412,7 +417,6 @@ impl ClusterInfo { addr_to_string(&ip_addr, &node.tvu_forwards), addr_to_string(&ip_addr, &node.repair), addr_to_string(&ip_addr, &node.serve_repair), - addr_to_string(&ip_addr, &node.storage_addr), addr_to_string(&ip_addr, &node.rpc), addr_to_string(&ip_addr, &node.rpc_pubsub), node.shred_version, @@ -423,9 +427,9 @@ impl ClusterInfo { format!( "IP Address |Age(ms)| Node identifier \ - |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR|Storag| RPC |PubSub|ShredVer\n\ - ------------------+-------+----------------------------------------------+\ - ------+------+------+------+------+------+------+------+------+------+--------\n\ + | Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\ + ------------------+-------+----------------------------------------------+---------------+\ + ------+------+------+------+------+------+------+------+------+--------\n\ {}\ Nodes: {}{}{}{}", nodes.join(""), @@ -440,7 +444,7 @@ impl ClusterInfo { } else { "".to_string() }, - if spy_nodes > 0 { + if different_shred_nodes > 0 { format!( "\nNodes with different shred version: {}", different_shred_nodes @@ -703,6 +707,18 @@ impl ClusterInfo { (vec, max) } + pub fn get_node_version(&self, pubkey: &Pubkey) -> Option { + self.gossip + .read() + .unwrap() + .crds + .table + .get(&CrdsValueLabel::Version(*pubkey)) + .map(|x| x.value.version()) + .flatten() + .map(|version| version.version.clone()) + } + /// all validators that have a valid rpc port regardless of `shred_version`. pub fn all_rpc_peers(&self) -> Vec { self.gossip @@ -1313,6 +1329,9 @@ impl ClusterInfo { let mut last_contact_info_trace = timestamp(); let mut adopt_shred_version = obj.my_shred_version() == 0; let recycler = PacketsRecycler::default(); + + let message = CrdsData::Version(Version::new(obj.id())); + obj.push_message(CrdsValue::new_signed(message, &obj.keypair)); loop { let start = timestamp(); thread_mem_usage::datapoint("solana-gossip"); diff --git a/core/src/crds_value.rs b/core/src/crds_value.rs index 5265c1c99a..26878d06f7 100644 --- a/core/src/crds_value.rs +++ b/core/src/crds_value.rs @@ -75,6 +75,7 @@ pub enum CrdsData { SnapshotHashes(SnapshotHash), AccountsHashes(SnapshotHash), EpochSlots(EpochSlotsIndex, EpochSlots), + Version(Version), } impl Sanitize for CrdsData { @@ -101,6 +102,7 @@ impl Sanitize for CrdsData { } val.sanitize() } + CrdsData::Version(version) => version.sanitize(), } } } @@ -206,6 +208,33 @@ impl Vote { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Version { + pub from: Pubkey, + pub wallclock: u64, + pub version: solana_version::Version, +} + +impl Sanitize for Version { + fn sanitize(&self) -> Result<(), SanitizeError> { + if self.wallclock >= MAX_WALLCLOCK { + return Err(SanitizeError::ValueOutOfBounds); + } + self.from.sanitize()?; + self.version.sanitize() + } +} + +impl Version { + pub fn new(from: Pubkey) -> Self { + Self { + from, + wallclock: timestamp(), + version: solana_version::Version::default(), + } + } +} + /// Type of the replicated value /// These are labels for values in a record that is associated with `Pubkey` #[derive(PartialEq, Hash, Eq, Clone, Debug)] @@ -216,6 +245,7 @@ pub enum CrdsValueLabel { SnapshotHashes(Pubkey), EpochSlots(EpochSlotsIndex, Pubkey), AccountsHashes(Pubkey), + Version(Pubkey), } impl fmt::Display for CrdsValueLabel { @@ -227,6 +257,7 @@ impl fmt::Display for CrdsValueLabel { CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHash({})", self.pubkey()), CrdsValueLabel::EpochSlots(ix, _) => write!(f, "EpochSlots({}, {})", ix, self.pubkey()), CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()), + CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()), } } } @@ -240,6 +271,7 @@ impl CrdsValueLabel { CrdsValueLabel::SnapshotHashes(p) => *p, CrdsValueLabel::EpochSlots(_, p) => *p, CrdsValueLabel::AccountsHashes(p) => *p, + CrdsValueLabel::Version(p) => *p, } } } @@ -257,7 +289,7 @@ impl CrdsValue { value.sign(keypair); value } - /// Totally unsecure unverfiable wallclock of the node that generated this message + /// Totally unsecure unverifiable wallclock of the node that generated this message /// Latest wallclock is always picked. /// This is used to time out push messages. pub fn wallclock(&self) -> u64 { @@ -268,6 +300,7 @@ impl CrdsValue { CrdsData::SnapshotHashes(hash) => hash.wallclock, CrdsData::AccountsHashes(hash) => hash.wallclock, CrdsData::EpochSlots(_, p) => p.wallclock, + CrdsData::Version(version) => version.wallclock, } } pub fn pubkey(&self) -> Pubkey { @@ -278,6 +311,7 @@ impl CrdsValue { CrdsData::SnapshotHashes(hash) => hash.from, CrdsData::AccountsHashes(hash) => hash.from, CrdsData::EpochSlots(_, p) => p.from, + CrdsData::Version(version) => version.from, } } pub fn label(&self) -> CrdsValueLabel { @@ -288,6 +322,7 @@ impl CrdsValue { CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()), CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()), CrdsData::EpochSlots(ix, _) => CrdsValueLabel::EpochSlots(*ix, self.pubkey()), + CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()), } } pub fn contact_info(&self) -> Option<&ContactInfo> { @@ -338,6 +373,13 @@ impl CrdsValue { } } + pub fn version(&self) -> Option<&Version> { + match &self.data { + CrdsData::Version(version) => Some(version), + _ => None, + } + } + /// Return all the possible labels for a record identified by Pubkey. pub fn record_labels(key: &Pubkey) -> Vec { let mut labels = vec![ @@ -345,6 +387,7 @@ impl CrdsValue { CrdsValueLabel::LowestSlot(*key), CrdsValueLabel::SnapshotHashes(*key), CrdsValueLabel::AccountsHashes(*key), + CrdsValueLabel::Version(*key), ]; labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key))); labels.extend((0..MAX_EPOCH_SLOTS).map(|ix| CrdsValueLabel::EpochSlots(ix, *key))); @@ -395,7 +438,7 @@ mod test { #[test] fn test_labels() { - let mut hits = [false; 4 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize]; + let mut hits = [false; 5 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize]; // this method should cover all the possible labels for v in &CrdsValue::record_labels(&Pubkey::default()) { match v { @@ -403,9 +446,10 @@ mod test { CrdsValueLabel::LowestSlot(_) => hits[1] = true, CrdsValueLabel::SnapshotHashes(_) => hits[2] = true, CrdsValueLabel::AccountsHashes(_) => hits[3] = true, - CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 4] = true, + CrdsValueLabel::Version(_) => hits[4] = true, + CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 5] = true, CrdsValueLabel::EpochSlots(ix, _) => { - hits[*ix as usize + MAX_VOTES as usize + 4] = true + hits[*ix as usize + MAX_VOTES as usize + 5] = true } } } diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 9d7af22634..4369e91c59 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -1474,7 +1474,7 @@ impl RpcSol for RpcSolImpl { fn get_version(&self, _: Self::Metadata) -> Result { Ok(RpcVersionInfo { - solana_core: solana_clap_utils::version!().to_string(), + solana_core: solana_version::Version::default().to_string(), }) } @@ -2637,7 +2637,7 @@ pub mod tests { let expected = json!({ "jsonrpc": "2.0", "result": { - "solana-core": solana_clap_utils::version!().to_string() + "solana-core": solana_version::version!().to_string() }, "id": 1 }); diff --git a/core/tests/client.rs b/core/tests/client.rs index 0c5755ee85..71be8443cb 100644 --- a/core/tests/client.rs +++ b/core/tests/client.rs @@ -46,7 +46,7 @@ fn test_rpc_client() { assert_eq!( client.get_version().unwrap().solana_core, - solana_clap_utils::version!() + solana_version::version!() ); assert!(client.get_account(&bob_pubkey).is_err()); diff --git a/version/.gitignore b/version/.gitignore new file mode 100644 index 0000000000..5404b132db --- /dev/null +++ b/version/.gitignore @@ -0,0 +1,2 @@ +/target/ +/farf/ diff --git a/version/Cargo.toml b/version/Cargo.toml new file mode 100644 index 0000000000..a06fb4fb50 --- /dev/null +++ b/version/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "solana-version" +version = "1.1.11" +description = "Solana Version" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +serde = "1.0.105" +serde_derive = "1.0.103" +solana-sdk = { path = "../sdk", version = "1.1.11" } + +[lib] +name = "solana_version" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/version/src/lib.rs b/version/src/lib.rs new file mode 100644 index 0000000000..b94683d89c --- /dev/null +++ b/version/src/lib.rs @@ -0,0 +1,49 @@ +extern crate serde_derive; +use serde_derive::{Deserialize, Serialize}; +use solana_sdk::sanitize::Sanitize; +use std::fmt; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Version { + major: u16, + minor: u16, + patch: u16, + commit: Option, // first 4 bytes of the sha1 commit hash +} + +impl Default for Version { + fn default() -> Self { + 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: option_env!("CI_COMMIT") + .map(|sha1| u32::from_str_radix(&sha1[..8], 16).unwrap()), + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}.{}.{} {}", + self.major, + self.minor, + self.patch, + match self.commit { + None => "devbuild".to_string(), + Some(commit) => format!("{:08x}", commit), + } + ) + } +} + +impl Sanitize for Version {} + +#[macro_export] +macro_rules! version { + () => { + &*format!("{}", $crate::Version::default()) + }; +}