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 cluster_info = cluster_info.clone();
|
||||||
let t_snapshot_packager = Builder::new()
|
let t_snapshot_packager = Builder::new()
|
||||||
.name("solana-snapshot-packager".to_string())
|
.name("solana-snapshot-packager".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || {
|
||||||
let mut hashes = vec![];
|
let mut hashes = vec![];
|
||||||
|
loop {
|
||||||
if exit.load(Ordering::Relaxed) {
|
if exit.load(Ordering::Relaxed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,9 @@ impl SnapshotPackagerService {
|
||||||
Ok(mut snapshot_package) => {
|
Ok(mut snapshot_package) => {
|
||||||
hashes.push((snapshot_package.root, snapshot_package.hash));
|
hashes.push((snapshot_package.root, snapshot_package.hash));
|
||||||
// Only package the latest
|
// 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;
|
snapshot_package = new_snapshot_package;
|
||||||
hashes.push((snapshot_package.root, snapshot_package.hash));
|
hashes.push((snapshot_package.root, snapshot_package.hash));
|
||||||
}
|
}
|
||||||
|
@ -56,6 +59,7 @@ impl SnapshotPackagerService {
|
||||||
Err(RecvTimeoutError::Disconnected) => break,
|
Err(RecvTimeoutError::Disconnected) => break,
|
||||||
Err(RecvTimeoutError::Timeout) => (),
|
Err(RecvTimeoutError::Timeout) => (),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -344,8 +344,36 @@ impl Validator {
|
||||||
.set_entrypoint(entrypoint_info.clone());
|
.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(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
|
cluster_info
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -55,6 +55,7 @@ mod tests {
|
||||||
snapshot_interval_slots,
|
snapshot_interval_slots,
|
||||||
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
|
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
|
||||||
snapshot_path: PathBuf::from(snapshot_dir.path()),
|
snapshot_path: PathBuf::from(snapshot_dir.path()),
|
||||||
|
trusted_validators: None,
|
||||||
};
|
};
|
||||||
bank_forks.set_snapshot_config(Some(snapshot_config.clone()));
|
bank_forks.set_snapshot_config(Some(snapshot_config.clone()));
|
||||||
SnapshotTestConfig {
|
SnapshotTestConfig {
|
||||||
|
|
|
@ -537,6 +537,7 @@ fn load_bank_forks(
|
||||||
snapshot_interval_slots: 0, // Value doesn't matter
|
snapshot_interval_slots: 0, // Value doesn't matter
|
||||||
snapshot_package_output_path: ledger_path.clone(),
|
snapshot_package_output_path: ledger_path.clone(),
|
||||||
snapshot_path: ledger_path.clone().join("snapshot"),
|
snapshot_path: ledger_path.clone().join("snapshot"),
|
||||||
|
trusted_validators: None,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let account_paths = if let Some(account_paths) = arg_matches.value_of("account_paths") {
|
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_measure::measure::Measure;
|
||||||
use solana_metrics::inc_new_counter_info;
|
use solana_metrics::inc_new_counter_info;
|
||||||
use solana_runtime::{bank::Bank, status_cache::MAX_CACHE_ENTRIES};
|
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::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
ops::Index,
|
ops::Index,
|
||||||
|
@ -26,6 +26,10 @@ pub struct SnapshotConfig {
|
||||||
|
|
||||||
// Where to place the snapshots for recent slots
|
// Where to place the snapshots for recent slots
|
||||||
pub snapshot_path: PathBuf,
|
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)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
@ -676,13 +676,14 @@ fn test_snapshot_restart_tower() {
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_snapshots_blockstore_floor() {
|
fn test_snapshots_blockstore_floor() {
|
||||||
|
solana_logger::setup();
|
||||||
// First set up the cluster with 1 snapshotting leader
|
// First set up the cluster with 1 snapshotting leader
|
||||||
let snapshot_interval_slots = 10;
|
let snapshot_interval_slots = 10;
|
||||||
let num_account_paths = 4;
|
let num_account_paths = 4;
|
||||||
|
|
||||||
let leader_snapshot_test_config =
|
let leader_snapshot_test_config =
|
||||||
setup_snapshot_validator_config(snapshot_interval_slots, num_account_paths);
|
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);
|
setup_snapshot_validator_config(snapshot_interval_slots, num_account_paths);
|
||||||
|
|
||||||
let snapshot_package_output_path = &leader_snapshot_test_config
|
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
|
// Start up a new node from a snapshot
|
||||||
let validator_stake = 5;
|
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(
|
cluster.add_validator(
|
||||||
&validator_snapshot_test_config.validator_config,
|
&validator_snapshot_test_config.validator_config,
|
||||||
validator_stake,
|
validator_stake,
|
||||||
|
@ -1000,6 +1012,7 @@ fn setup_snapshot_validator_config(
|
||||||
snapshot_interval_slots,
|
snapshot_interval_slots,
|
||||||
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
|
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
|
||||||
snapshot_path: PathBuf::from(snapshot_dir.path()),
|
snapshot_path: PathBuf::from(snapshot_dir.path()),
|
||||||
|
trusted_validators: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the account paths
|
// Create the account paths
|
||||||
|
|
|
@ -139,6 +139,9 @@ while [[ -n $1 ]]; do
|
||||||
elif [[ $1 = --log ]]; then
|
elif [[ $1 = --log ]]; then
|
||||||
args+=("$1" "$2")
|
args+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
|
elif [[ $1 = --trusted-validator ]]; then
|
||||||
|
args+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
elif [[ $1 = -h ]]; then
|
elif [[ $1 = -h ]]; then
|
||||||
usage "$@"
|
usage "$@"
|
||||||
else
|
else
|
||||||
|
|
|
@ -8,7 +8,7 @@ use log::*;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use solana_clap_utils::{
|
use solana_clap_utils::{
|
||||||
input_parsers::pubkey_of,
|
input_parsers::pubkey_of,
|
||||||
input_validators::{is_keypair, is_pubkey_or_keypair},
|
input_validators::{is_keypair, is_pubkey, is_pubkey_or_keypair},
|
||||||
keypair::{
|
keypair::{
|
||||||
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
|
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
|
||||||
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||||
|
@ -32,6 +32,7 @@ use solana_sdk::{
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{self, Read},
|
io::{self, Read},
|
||||||
net::{SocketAddr, TcpListener},
|
net::{SocketAddr, TcpListener},
|
||||||
|
@ -201,6 +202,7 @@ fn get_rpc_addr(
|
||||||
identity_keypair: &Arc<Keypair>,
|
identity_keypair: &Arc<Keypair>,
|
||||||
entrypoint_gossip: &SocketAddr,
|
entrypoint_gossip: &SocketAddr,
|
||||||
expected_shred_version: Option<u16>,
|
expected_shred_version: Option<u16>,
|
||||||
|
trusted_validators: Option<&HashSet<Pubkey>>,
|
||||||
snapshot_not_required: bool,
|
snapshot_not_required: bool,
|
||||||
) -> (RpcClient, SocketAddr) {
|
) -> (RpcClient, SocketAddr) {
|
||||||
let mut cluster_info = ClusterInfo::new(
|
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() {
|
if rpc_peers.is_empty() {
|
||||||
info!("No RPC services found ");
|
info!("No RPC services found ");
|
||||||
} else {
|
} else {
|
||||||
|
@ -262,9 +280,16 @@ fn get_rpc_addr(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_snapshot_hash_for_node(&rpc_peer.id)
|
.get_snapshot_hash_for_node(&rpc_peer.id)
|
||||||
{
|
{
|
||||||
let highest_snapshot_slot_for_node = snapshot_hash
|
let highest_snapshot_slot_for_node =
|
||||||
.iter()
|
snapshot_hash.iter().fold(0, |highest_slot, snapshot_hash| {
|
||||||
.fold(0, |a, (slot, _hash)| a.max(*slot));
|
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 {
|
if highest_snapshot_slot_for_node > highest_snapshot_slot {
|
||||||
// Found a higher snapshot, remove all rpc peers with a lower snapshot
|
// Found a higher snapshot, remove all rpc peers with a lower snapshot
|
||||||
|
@ -682,6 +707,16 @@ pub fn main() {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Add a hard fork at this slot"),
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
let identity_keypair = Arc::new(
|
let identity_keypair = Arc::new(
|
||||||
|
@ -723,6 +758,13 @@ pub fn main() {
|
||||||
exit(1);
|
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 {
|
let mut validator_config = ValidatorConfig {
|
||||||
blockstream_unix_socket: matches
|
blockstream_unix_socket: matches
|
||||||
.value_of("blockstream_unix_socket")
|
.value_of("blockstream_unix_socket")
|
||||||
|
@ -794,6 +836,7 @@ pub fn main() {
|
||||||
},
|
},
|
||||||
snapshot_path,
|
snapshot_path,
|
||||||
snapshot_package_output_path: ledger_path.clone(),
|
snapshot_package_output_path: ledger_path.clone(),
|
||||||
|
trusted_validators,
|
||||||
});
|
});
|
||||||
|
|
||||||
if matches.is_present("limit_ledger_size") {
|
if matches.is_present("limit_ledger_size") {
|
||||||
|
@ -956,6 +999,12 @@ pub fn main() {
|
||||||
&identity_keypair,
|
&identity_keypair,
|
||||||
&cluster_entrypoint.gossip,
|
&cluster_entrypoint.gossip,
|
||||||
validator_config.expected_shred_version,
|
validator_config.expected_shred_version,
|
||||||
|
validator_config
|
||||||
|
.snapshot_config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.trusted_validators
|
||||||
|
.as_ref(),
|
||||||
no_snapshot_fetch,
|
no_snapshot_fetch,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue