Add --trusted-validator support for snapshot hash validation (#8390)
This commit is contained in:
parent
223f9707ca
commit
b7386f9d84
|
@ -28,8 +28,9 @@ 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![];
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
@ -38,7 +39,9 @@ impl SnapshotPackagerService {
|
|||
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() {
|
||||
while let Ok(new_snapshot_package) =
|
||||
snapshot_package_receiver.try_recv()
|
||||
{
|
||||
snapshot_package = new_snapshot_package;
|
||||
hashes.push((snapshot_package.root, snapshot_package.hash));
|
||||
}
|
||||
|
@ -56,6 +59,7 @@ impl SnapshotPackagerService {
|
|||
Err(RecvTimeoutError::Disconnected) => break,
|
||||
Err(RecvTimeoutError::Timeout) => (),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Self {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue