Add --trusted-validator support for snapshot hash validation (#8390)

This commit is contained in:
sakridge 2020-02-21 18:42:24 -08:00 committed by GitHub
parent 223f9707ca
commit b7386f9d84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 32 deletions

View File

@ -28,33 +28,37 @@ impl SnapshotPackagerService {
let cluster_info = cluster_info.clone();
let t_snapshot_packager = Builder::new()
.name("solana-snapshot-packager".to_string())
.spawn(move || loop {
.spawn(move || {
let mut hashes = vec![];
if exit.load(Ordering::Relaxed) {
break;
}
match snapshot_package_receiver.recv_timeout(Duration::from_secs(1)) {
Ok(mut snapshot_package) => {
hashes.push((snapshot_package.root, snapshot_package.hash));
// Only package the latest
while let Ok(new_snapshot_package) = snapshot_package_receiver.try_recv() {
snapshot_package = new_snapshot_package;
hashes.push((snapshot_package.root, snapshot_package.hash));
}
if let Err(err) = archive_snapshot_package(&snapshot_package) {
warn!("Failed to create snapshot archive: {}", err);
}
while hashes.len() > MAX_SNAPSHOT_HASHES {
hashes.remove(0);
}
cluster_info
.write()
.unwrap()
.push_snapshot_hashes(hashes.clone());
loop {
if exit.load(Ordering::Relaxed) {
break;
}
match snapshot_package_receiver.recv_timeout(Duration::from_secs(1)) {
Ok(mut snapshot_package) => {
hashes.push((snapshot_package.root, snapshot_package.hash));
// Only package the latest
while let Ok(new_snapshot_package) =
snapshot_package_receiver.try_recv()
{
snapshot_package = new_snapshot_package;
hashes.push((snapshot_package.root, snapshot_package.hash));
}
if let Err(err) = archive_snapshot_package(&snapshot_package) {
warn!("Failed to create snapshot archive: {}", err);
}
while hashes.len() > MAX_SNAPSHOT_HASHES {
hashes.remove(0);
}
cluster_info
.write()
.unwrap()
.push_snapshot_hashes(hashes.clone());
}
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => (),
}
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => (),
}
})
.unwrap();

View File

@ -344,8 +344,36 @@ impl Validator {
.set_entrypoint(entrypoint_info.clone());
}
// If the node was loaded from a snapshot, advertise it in gossip
if let Some(snapshot_hash) = snapshot_hash {
if let Some(ref trusted_validators) =
config.snapshot_config.as_ref().unwrap().trusted_validators
{
let mut trusted = false;
for _ in 0..10 {
trusted = cluster_info
.read()
.unwrap()
.get_snapshot_hash(snapshot_hash.0)
.iter()
.any(|(pubkey, hash)| {
trusted_validators.contains(pubkey) && snapshot_hash.1 == *hash
});
if trusted {
break;
}
sleep(Duration::from_secs(1));
}
if !trusted {
error!(
"The snapshot hash for slot {} is not published by your trusted validators: {:?}",
snapshot_hash.0, trusted_validators
);
process::exit(1);
}
}
// If the node was loaded from a snapshot, advertise it in gossip
cluster_info
.write()
.unwrap()

View File

@ -55,6 +55,7 @@ mod tests {
snapshot_interval_slots,
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
snapshot_path: PathBuf::from(snapshot_dir.path()),
trusted_validators: None,
};
bank_forks.set_snapshot_config(Some(snapshot_config.clone()));
SnapshotTestConfig {

View File

@ -537,6 +537,7 @@ fn load_bank_forks(
snapshot_interval_slots: 0, // Value doesn't matter
snapshot_package_output_path: ledger_path.clone(),
snapshot_path: ledger_path.clone().join("snapshot"),
trusted_validators: None,
})
};
let account_paths = if let Some(account_paths) = arg_matches.value_of("account_paths") {

View File

@ -6,7 +6,7 @@ use log::*;
use solana_measure::measure::Measure;
use solana_metrics::inc_new_counter_info;
use solana_runtime::{bank::Bank, status_cache::MAX_CACHE_ENTRIES};
use solana_sdk::{clock::Slot, timing};
use solana_sdk::{clock::Slot, pubkey::Pubkey, timing};
use std::{
collections::{HashMap, HashSet},
ops::Index,
@ -26,6 +26,10 @@ pub struct SnapshotConfig {
// Where to place the snapshots for recent slots
pub snapshot_path: PathBuf,
// Validators that must vouch for a given snapshot hash before it's accepted
// None = accept any snapshot hash
pub trusted_validators: Option<HashSet<Pubkey>>,
}
#[derive(Error, Debug)]

View File

@ -676,13 +676,14 @@ fn test_snapshot_restart_tower() {
#[test]
#[serial]
fn test_snapshots_blockstore_floor() {
solana_logger::setup();
// First set up the cluster with 1 snapshotting leader
let snapshot_interval_slots = 10;
let num_account_paths = 4;
let leader_snapshot_test_config =
setup_snapshot_validator_config(snapshot_interval_slots, num_account_paths);
let validator_snapshot_test_config =
let mut validator_snapshot_test_config =
setup_snapshot_validator_config(snapshot_interval_slots, num_account_paths);
let snapshot_package_output_path = &leader_snapshot_test_config
@ -721,6 +722,17 @@ fn test_snapshots_blockstore_floor() {
// Start up a new node from a snapshot
let validator_stake = 5;
let (cluster_nodes, _) = discover_cluster(&cluster.entry_point_info.gossip, 1).unwrap();
let mut trusted_validators = HashSet::new();
trusted_validators.insert(cluster_nodes[0].id);
if let Some(ref mut config) = validator_snapshot_test_config
.validator_config
.snapshot_config
{
config.trusted_validators = Some(trusted_validators);
}
cluster.add_validator(
&validator_snapshot_test_config.validator_config,
validator_stake,
@ -1000,6 +1012,7 @@ fn setup_snapshot_validator_config(
snapshot_interval_slots,
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
snapshot_path: PathBuf::from(snapshot_dir.path()),
trusted_validators: None,
};
// Create the account paths

View File

@ -139,6 +139,9 @@ while [[ -n $1 ]]; do
elif [[ $1 = --log ]]; then
args+=("$1" "$2")
shift 2
elif [[ $1 = --trusted-validator ]]; then
args+=("$1" "$2")
shift 2
elif [[ $1 = -h ]]; then
usage "$@"
else

View File

@ -8,7 +8,7 @@ use log::*;
use rand::{thread_rng, Rng};
use solana_clap_utils::{
input_parsers::pubkey_of,
input_validators::{is_keypair, is_pubkey_or_keypair},
input_validators::{is_keypair, is_pubkey, is_pubkey_or_keypair},
keypair::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
@ -32,6 +32,7 @@ use solana_sdk::{
signature::{Keypair, Signer},
};
use std::{
collections::HashSet,
fs::{self, File},
io::{self, Read},
net::{SocketAddr, TcpListener},
@ -201,6 +202,7 @@ fn get_rpc_addr(
identity_keypair: &Arc<Keypair>,
entrypoint_gossip: &SocketAddr,
expected_shred_version: Option<u16>,
trusted_validators: Option<&HashSet<Pubkey>>,
snapshot_not_required: bool,
) -> (RpcClient, SocketAddr) {
let mut cluster_info = ClusterInfo::new(
@ -247,6 +249,22 @@ fn get_rpc_addr(
}
}
let trusted_slots = if let Some(trusted_validators) = trusted_validators {
let trusted_slots = HashSet::new();
for trusted_validator in trusted_validators {
if let Some(slot_hash) = cluster_info
.read()
.unwrap()
.get_snapshot_hash_for_node(trusted_validator)
{
trusted_slots.union(&slot_hash.iter().collect());
}
}
Some(trusted_slots)
} else {
None
};
if rpc_peers.is_empty() {
info!("No RPC services found ");
} else {
@ -262,9 +280,16 @@ fn get_rpc_addr(
.unwrap()
.get_snapshot_hash_for_node(&rpc_peer.id)
{
let highest_snapshot_slot_for_node = snapshot_hash
.iter()
.fold(0, |a, (slot, _hash)| a.max(*slot));
let highest_snapshot_slot_for_node =
snapshot_hash.iter().fold(0, |highest_slot, snapshot_hash| {
if let Some(ref trusted_slots) = trusted_slots {
if !trusted_slots.contains(snapshot_hash) {
// Ignore all untrusted slots
return highest_slot;
}
}
highest_slot.max(snapshot_hash.0)
});
if highest_snapshot_slot_for_node > highest_snapshot_slot {
// Found a higher snapshot, remove all rpc peers with a lower snapshot
@ -682,6 +707,16 @@ pub fn main() {
.takes_value(true)
.help("Add a hard fork at this slot"),
)
.arg(
Arg::with_name("trusted_validators")
.long("trusted-validator")
.validator(is_pubkey)
.value_name("PUBKEY")
.multiple(true)
.takes_value(true)
.help("A snapshot hash must be published in gossip by this validator to be accepted. \
May be specified multiple times. If unspecified any snapshot hash will be accepted"),
)
.get_matches();
let identity_keypair = Arc::new(
@ -723,6 +758,13 @@ pub fn main() {
exit(1);
});
let trusted_validators = if matches.is_present("trusted_validators") {
let trusted_validators = values_t_or_exit!(matches, "trusted_validators", Pubkey);
Some(trusted_validators.into_iter().collect())
} else {
None
};
let mut validator_config = ValidatorConfig {
blockstream_unix_socket: matches
.value_of("blockstream_unix_socket")
@ -794,6 +836,7 @@ pub fn main() {
},
snapshot_path,
snapshot_package_output_path: ledger_path.clone(),
trusted_validators,
});
if matches.is_present("limit_ledger_size") {
@ -956,6 +999,12 @@ pub fn main() {
&identity_keypair,
&cluster_entrypoint.gossip,
validator_config.expected_shred_version,
validator_config
.snapshot_config
.as_ref()
.unwrap()
.trusted_validators
.as_ref(),
no_snapshot_fetch,
);