v1.0: Advertise node version in gossip (bp #9986) (#9995)

automerge
This commit is contained in:
mergify[bot]
2020-05-12 19:38:52 -07:00
committed by GitHub
parent 5326f3ec73
commit 14bbcef722
13 changed files with 2523 additions and 2343 deletions

4650
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,7 @@ members = [
"transaction-status",
"upload-perf",
"net-utils",
"version",
"vote-signer",
"cli",
"rayon-threadlimit",

View File

@ -108,6 +108,8 @@ pub struct RpcContactInfo {
pub tpu: Option<SocketAddr>,
/// JSON RPC port
pub rpc: Option<SocketAddr>,
/// Software version
pub version: Option<String>,
}
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot

View File

@ -61,6 +61,7 @@ solana-runtime = { path = "../runtime", version = "1.0.23" }
solana-sdk = { path = "../sdk", version = "1.0.23" }
solana-stake-program = { path = "../programs/stake", version = "1.0.23" }
solana-storage-program = { path = "../programs/storage", version = "1.0.23" }
solana-version = { path = "../version", version = "1.0.23" }
solana-vote-program = { path = "../programs/vote", version = "1.0.23" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.23" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.23" }

View File

@ -20,7 +20,8 @@ use crate::{
crds_gossip_error::CrdsGossipError,
crds_gossip_pull::{CrdsFilter, CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS},
crds_value::{
self, CrdsData, CrdsValue, CrdsValueLabel, EpochSlots, SnapshotHash, Vote, MAX_WALLCLOCK,
self, CrdsData, CrdsValue, CrdsValueLabel, EpochSlots, SnapshotHash, Version, Vote,
MAX_WALLCLOCK,
},
packet::{Packet, PACKET_DATA_SIZE},
result::{Error, Result},
@ -336,6 +337,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
@ -351,10 +353,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 {
@ -363,6 +364,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),
@ -370,7 +376,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,
@ -381,9 +386,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(""),
@ -398,7 +403,7 @@ impl ClusterInfo {
} else {
"".to_string()
},
if spy_nodes > 0 {
if different_shred_nodes > 0 {
format!(
"\nNodes with different shred version: {}",
different_shred_nodes
@ -556,6 +561,16 @@ impl ClusterInfo {
.map(|x| x.value.contact_info().unwrap())
}
pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> {
self.gossip
.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<ContactInfo> {
let me = self.my_data();
@ -1201,6 +1216,14 @@ impl ClusterInfo {
let mut last_contact_info_trace = timestamp();
let mut adopt_shred_version = obj.read().unwrap().my_data().shred_version == 0;
let recycler = PacketsRecycler::default();
{
let mut obj = obj.write().unwrap();
let message = CrdsValue::new_signed(
CrdsData::Version(Version::new(obj.id())),
&obj.keypair,
);
obj.push_message(message);
}
loop {
let start = timestamp();
thread_mem_usage::datapoint("solana-gossip");

View File

@ -71,6 +71,7 @@ pub enum CrdsData {
EpochSlots(EpochSlotIndex, EpochSlots),
SnapshotHashes(SnapshotHash),
AccountsHashes(SnapshotHash),
Version(Version),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@ -122,6 +123,7 @@ impl Sanitize for CrdsData {
}
val.sanitize()
}
CrdsData::Version(version) => version.sanitize(),
}
}
}
@ -228,6 +230,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)]
@ -237,6 +266,7 @@ pub enum CrdsValueLabel {
EpochSlots(Pubkey),
SnapshotHashes(Pubkey),
AccountsHashes(Pubkey),
Version(Pubkey),
}
impl fmt::Display for CrdsValueLabel {
@ -247,6 +277,7 @@ impl fmt::Display for CrdsValueLabel {
CrdsValueLabel::EpochSlots(_) => write!(f, "EpochSlots({})", self.pubkey()),
CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHashes({})", self.pubkey()),
CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()),
CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()),
}
}
}
@ -259,6 +290,7 @@ impl CrdsValueLabel {
CrdsValueLabel::EpochSlots(p) => *p,
CrdsValueLabel::SnapshotHashes(p) => *p,
CrdsValueLabel::AccountsHashes(p) => *p,
CrdsValueLabel::Version(p) => *p,
}
}
}
@ -276,7 +308,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 {
@ -286,6 +318,7 @@ impl CrdsValue {
CrdsData::EpochSlots(_, vote) => vote.wallclock,
CrdsData::SnapshotHashes(hash) => hash.wallclock,
CrdsData::AccountsHashes(hash) => hash.wallclock,
CrdsData::Version(version) => version.wallclock,
}
}
pub fn pubkey(&self) -> Pubkey {
@ -295,6 +328,7 @@ impl CrdsValue {
CrdsData::EpochSlots(_, slots) => slots.from,
CrdsData::SnapshotHashes(hash) => hash.from,
CrdsData::AccountsHashes(hash) => hash.from,
CrdsData::Version(version) => version.from,
}
}
pub fn label(&self) -> CrdsValueLabel {
@ -304,6 +338,7 @@ impl CrdsValue {
CrdsData::EpochSlots(_, _) => CrdsValueLabel::EpochSlots(self.pubkey()),
CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()),
CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()),
CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()),
}
}
pub fn contact_info(&self) -> Option<&ContactInfo> {
@ -347,6 +382,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<CrdsValueLabel> {
let mut labels = vec![
@ -354,6 +396,7 @@ impl CrdsValue {
CrdsValueLabel::EpochSlots(*key),
CrdsValueLabel::SnapshotHashes(*key),
CrdsValueLabel::AccountsHashes(*key),
CrdsValueLabel::Version(*key),
];
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
labels
@ -403,7 +446,7 @@ mod test {
#[test]
fn test_labels() {
let mut hits = [false; 4 + MAX_VOTES as usize];
let mut hits = [false; 5 + MAX_VOTES as usize];
// this method should cover all the possible labels
for v in &CrdsValue::record_labels(&Pubkey::default()) {
match v {
@ -411,7 +454,8 @@ mod test {
CrdsValueLabel::EpochSlots(_) => 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,
}
}
assert!(hits.iter().all(|x| *x));

View File

@ -1066,6 +1066,9 @@ impl RpcSol for RpcSolImpl {
gossip: Some(contact_info.gossip),
tpu: valid_address_or_none(&contact_info.tpu),
rpc: valid_address_or_none(&contact_info.rpc),
version: cluster_info
.get_node_version(&contact_info.id)
.map(|v| v.to_string()),
})
} else {
None // Exclude spy nodes
@ -1468,7 +1471,7 @@ impl RpcSol for RpcSolImpl {
fn get_version(&self, _: Self::Metadata) -> Result<RpcVersionInfo> {
Ok(RpcVersionInfo {
solana_core: solana_clap_utils::version!().to_string(),
solana_core: solana_version::Version::default().to_string(),
})
}
@ -1835,7 +1838,7 @@ pub mod tests {
.expect("actual response deserialization");
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:{}"}}],"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}}],"id":1}}"#,
leader_pubkey,
rpc_port::DEFAULT_RPC_PORT
);
@ -2637,7 +2640,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
});

View File

@ -40,7 +40,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());

View File

@ -258,7 +258,8 @@ The result field will be an array of JSON objects, each with the following sub f
* `pubkey: <string>` - Node public key, as base-58 encoded string
* `gossip: <string>` - Gossip network address for the node
* `tpu: <string>` - TPU network address for the node
* `rpc: <string>` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
* `rpc: <string>|null` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
* `version: <string>|null` - The software version of the node, or `null` if the version information is not available
#### Example:
@ -267,7 +268,7 @@ The result field will be an array of JSON objects, each with the following sub f
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"},"version":"1.0.0 c375ce1f"],"id":1}
```
### getConfirmedBlock

View File

@ -27,6 +27,7 @@ use solana_ledger::bank_forks::SnapshotConfig;
use solana_perf::recycler::enable_recycler_warming;
use solana_sdk::{
clock::Slot,
commitment_config::CommitmentConfig,
genesis_config::GenesisConfig,
hash::Hash,
pubkey::Pubkey,
@ -398,8 +399,10 @@ fn check_vote_account(
node_pubkey: &Pubkey,
) -> Result<(), String> {
let found_vote_account = rpc_client
.get_account(vote_pubkey)
.map_err(|err| format!("Failed to get vote account: {}", err.to_string()))?;
.get_account_with_commitment(vote_pubkey, CommitmentConfig::root())
.map_err(|err| format!("Failed to get vote account: {}", err.to_string()))?
.value
.ok_or_else(|| format!("vote account does not exist: {}", vote_pubkey))?;
if found_vote_account.owner != solana_vote_program::id() {
return Err(format!(
@ -409,8 +412,10 @@ fn check_vote_account(
}
let found_node_account = rpc_client
.get_account(node_pubkey)
.map_err(|err| format!("Failed to get identity account: {}", err.to_string()))?;
.get_account_with_commitment(node_pubkey, CommitmentConfig::root())
.map_err(|err| format!("Failed to get identity account: {}", err.to_string()))?
.value
.ok_or_else(|| format!("identity account does not exist: {}", node_pubkey))?;
let found_vote_account = solana_vote_program::vote_state::VoteState::from(&found_vote_account);
if let Some(found_vote_account) = found_vote_account {
@ -1282,7 +1287,7 @@ pub fn main() {
})
.and_then(|_| {
if let Some(snapshot_hash) = snapshot_hash {
rpc_client.get_slot()
rpc_client.get_slot_with_commitment(CommitmentConfig::root())
.map_err(|err| format!("Failed to get RPC node slot: {}", err))
.and_then(|slot| {
info!("RPC node root slot: {}", slot);

2
version/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
/farf/

20
version/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "solana-version"
version = "1.1.11"
description = "Solana Version"
authors = ["Solana Maintainers <maintainers@solana.com>"]
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.0.23" }
[lib]
name = "solana_version"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

70
version/src/lib.rs Normal file
View File

@ -0,0 +1,70 @@
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<u32>, // first 4 bytes of the sha1 commit hash
}
fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
let sha1 = sha1?;
if sha1.len() < 8 {
None
} else {
u32::from_str_radix(&sha1[..8], 16).ok()
}
}
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: compute_commit(option_env!("CI_COMMIT")),
}
}
}
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())
};
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_compute_commit() {
assert_eq!(compute_commit(None), None);
assert_eq!(compute_commit(Some("1234567890")), Some(0x12345678));
assert_eq!(compute_commit(Some("HEAD")), None);
assert_eq!(compute_commit(Some("garbagein")), None);
}
}