Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit d5faa6e

Browse files
authored
Local Cluster Duplicate Switch Test (#32614)
* Add test for broken behavior in same batch * tests * redo test * Important fixes to not immediately duplicate confirm by adding extra node * Fixup merge * PR comments * Redo stakes * clippy * fixes * Resolve conflicts * add thread logging * Fixup merge * Fixup bugs * Revert "add thread logging" This reverts commit 9dc2240. * Hide scope * Fixes * Cleanup test_faulty_node * More fixes * Fixes * Error logging * Fix duplicate confirmed * done * PR comments * Revert "Error logging" This reverts commit 18953c3. * PR comments * nit
1 parent 8e4a9a9 commit d5faa6e

File tree

7 files changed

+641
-64
lines changed

7 files changed

+641
-64
lines changed

ledger/src/blockstore.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,10 @@ impl Blockstore {
32203220
self.dead_slots_cf.delete(slot)
32213221
}
32223222

3223+
pub fn remove_slot_duplicate_proof(&self, slot: Slot) -> Result<()> {
3224+
self.duplicate_slots_cf.delete(slot)
3225+
}
3226+
32233227
pub fn store_duplicate_if_not_existing(
32243228
&self,
32253229
slot: Slot,
@@ -3233,6 +3237,15 @@ impl Blockstore {
32333237
}
32343238
}
32353239

3240+
pub fn get_first_duplicate_proof(&self) -> Option<(Slot, DuplicateSlotProof)> {
3241+
let mut iter = self
3242+
.db
3243+
.iter::<cf::DuplicateSlots>(IteratorMode::From(0, IteratorDirection::Forward))
3244+
.unwrap();
3245+
iter.next()
3246+
.map(|(slot, proof_bytes)| (slot, deserialize(&proof_bytes).unwrap()))
3247+
}
3248+
32363249
pub fn store_duplicate_slot(&self, slot: Slot, shred1: Vec<u8>, shred2: Vec<u8>) -> Result<()> {
32373250
let duplicate_slot_proof = DuplicateSlotProof::new(shred1, shred2);
32383251
self.duplicate_slots_cf.put(slot, &duplicate_slot_proof)

local-cluster/src/cluster.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use {
22
solana_client::thin_client::ThinClient,
33
solana_core::validator::{Validator, ValidatorConfig},
44
solana_gossip::{cluster_info::Node, contact_info::ContactInfo},
5+
solana_ledger::shred::Shred,
56
solana_sdk::{pubkey::Pubkey, signature::Keypair},
67
solana_streamer::socket::SocketAddrSpace,
78
std::{path::PathBuf, sync::Arc},
@@ -62,4 +63,6 @@ pub trait Cluster {
6263
config: ValidatorConfig,
6364
socket_addr_space: SocketAddrSpace,
6465
);
66+
fn set_entry_point(&mut self, entry_point_info: ContactInfo);
67+
fn send_shreds_to_validator(&self, dup_shreds: Vec<&Shred>, validator_key: &Pubkey);
6568
}

local-cluster/src/local_cluster.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ use {
1313
},
1414
solana_gossip::{
1515
cluster_info::Node,
16-
contact_info::{ContactInfo, LegacyContactInfo},
16+
contact_info::{ContactInfo, LegacyContactInfo, Protocol},
1717
gossip_service::discover_cluster,
1818
},
19-
solana_ledger::create_new_tmp_ledger,
19+
solana_ledger::{create_new_tmp_ledger, shred::Shred},
2020
solana_runtime::{
2121
genesis_utils::{
2222
create_genesis_config_with_vote_accounts_and_cluster_type, GenesisConfigInfo,
@@ -57,6 +57,7 @@ use {
5757
collections::HashMap,
5858
io::{Error, ErrorKind, Result},
5959
iter,
60+
net::UdpSocket,
6061
path::{Path, PathBuf},
6162
sync::{Arc, RwLock},
6263
},
@@ -852,6 +853,10 @@ impl Cluster for LocalCluster {
852853
(node, entry_point_info)
853854
}
854855

856+
fn set_entry_point(&mut self, entry_point_info: ContactInfo) {
857+
self.entry_point_info = entry_point_info;
858+
}
859+
855860
fn restart_node(
856861
&mut self,
857862
pubkey: &Pubkey,
@@ -922,6 +927,20 @@ impl Cluster for LocalCluster {
922927
fn get_contact_info(&self, pubkey: &Pubkey) -> Option<&ContactInfo> {
923928
self.validators.get(pubkey).map(|v| &v.info.contact_info)
924929
}
930+
931+
fn send_shreds_to_validator(&self, dup_shreds: Vec<&Shred>, validator_key: &Pubkey) {
932+
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
933+
let validator_tvu = self
934+
.get_contact_info(validator_key)
935+
.unwrap()
936+
.tvu(Protocol::UDP)
937+
.unwrap();
938+
for shred in dup_shreds {
939+
send_socket
940+
.send_to(shred.payload().as_ref(), validator_tvu)
941+
.unwrap();
942+
}
943+
}
925944
}
926945

927946
impl Drop for LocalCluster {

local-cluster/tests/common/mod.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ pub fn restore_tower(tower_path: &Path, node_pubkey: &Pubkey) -> Option<Tower> {
7777
Tower::restore(&file_tower_storage, node_pubkey).ok()
7878
}
7979

80+
pub fn remove_tower_if_exists(tower_path: &Path, node_pubkey: &Pubkey) {
81+
let file_tower_storage = FileTowerStorage::new(tower_path.to_path_buf());
82+
let filename = file_tower_storage.filename(node_pubkey);
83+
if filename.exists() {
84+
fs::remove_file(file_tower_storage.filename(node_pubkey)).unwrap();
85+
}
86+
}
87+
8088
pub fn remove_tower(tower_path: &Path, node_pubkey: &Pubkey) {
8189
let file_tower_storage = FileTowerStorage::new(tower_path.to_path_buf());
8290
fs::remove_file(file_tower_storage.filename(node_pubkey)).unwrap();
@@ -120,17 +128,18 @@ pub fn purge_slots_with_count(blockstore: &Blockstore, start_slot: Slot, slot_co
120128
pub fn wait_for_last_vote_in_tower_to_land_in_ledger(
121129
ledger_path: &Path,
122130
node_pubkey: &Pubkey,
123-
) -> Slot {
124-
let (last_vote, _) = last_vote_in_tower(ledger_path, node_pubkey).unwrap();
125-
loop {
126-
// We reopen in a loop to make sure we get updates
127-
let blockstore = open_blockstore(ledger_path);
128-
if blockstore.is_full(last_vote) {
129-
break;
131+
) -> Option<Slot> {
132+
last_vote_in_tower(ledger_path, node_pubkey).map(|(last_vote, _)| {
133+
loop {
134+
// We reopen in a loop to make sure we get updates
135+
let blockstore = open_blockstore(ledger_path);
136+
if blockstore.is_full(last_vote) {
137+
break;
138+
}
139+
sleep(Duration::from_millis(100));
130140
}
131-
sleep(Duration::from_millis(100));
132-
}
133-
last_vote
141+
last_vote
142+
})
134143
}
135144

136145
pub fn copy_blocks(end_slot: Slot, source: &Blockstore, dest: &Blockstore) {
@@ -390,40 +399,66 @@ pub fn run_cluster_partition<C>(
390399
on_partition_resolved(&mut cluster, &mut context);
391400
}
392401

402+
pub struct ValidatorTestConfig {
403+
pub validator_keypair: Arc<Keypair>,
404+
pub validator_config: ValidatorConfig,
405+
pub in_genesis: bool,
406+
}
407+
393408
pub fn test_faulty_node(
394409
faulty_node_type: BroadcastStageType,
395410
node_stakes: Vec<u64>,
411+
validator_test_configs: Option<Vec<ValidatorTestConfig>>,
412+
custom_leader_schedule: Option<FixedSchedule>,
396413
) -> (LocalCluster, Vec<Arc<Keypair>>) {
397-
solana_logger::setup_with_default("solana_local_cluster=info");
398414
let num_nodes = node_stakes.len();
399-
let mut validator_keys = Vec::with_capacity(num_nodes);
400-
validator_keys.resize_with(num_nodes, || (Arc::new(Keypair::new()), true));
415+
let validator_keys = validator_test_configs
416+
.as_ref()
417+
.map(|configs| {
418+
configs
419+
.iter()
420+
.map(|config| (config.validator_keypair.clone(), config.in_genesis))
421+
.collect()
422+
})
423+
.unwrap_or_else(|| {
424+
let mut validator_keys = Vec::with_capacity(num_nodes);
425+
validator_keys.resize_with(num_nodes, || (Arc::new(Keypair::new()), true));
426+
validator_keys
427+
});
428+
401429
assert_eq!(node_stakes.len(), num_nodes);
402430
assert_eq!(validator_keys.len(), num_nodes);
403431

404-
// Use a fixed leader schedule so that only the faulty node gets leader slots.
405-
let validator_to_slots = vec![(
406-
validator_keys[0].0.as_ref().pubkey(),
407-
solana_sdk::clock::DEFAULT_DEV_SLOTS_PER_EPOCH as usize,
408-
)];
409-
let leader_schedule = create_custom_leader_schedule(validator_to_slots.into_iter());
410-
let fixed_leader_schedule = Some(FixedSchedule {
411-
leader_schedule: Arc::new(leader_schedule),
432+
let fixed_leader_schedule = custom_leader_schedule.unwrap_or_else(|| {
433+
// Use a fixed leader schedule so that only the faulty node gets leader slots.
434+
let validator_to_slots = vec![(
435+
validator_keys[0].0.as_ref().pubkey(),
436+
solana_sdk::clock::DEFAULT_DEV_SLOTS_PER_EPOCH as usize,
437+
)];
438+
let leader_schedule = create_custom_leader_schedule(validator_to_slots.into_iter());
439+
FixedSchedule {
440+
leader_schedule: Arc::new(leader_schedule),
441+
}
412442
});
413443

414-
let error_validator_config = ValidatorConfig {
415-
broadcast_stage_type: faulty_node_type,
416-
fixed_leader_schedule: fixed_leader_schedule.clone(),
417-
..ValidatorConfig::default_for_test()
418-
};
419-
let mut validator_configs = Vec::with_capacity(num_nodes);
444+
let mut validator_configs = validator_test_configs
445+
.map(|configs| {
446+
configs
447+
.into_iter()
448+
.map(|config| config.validator_config)
449+
.collect()
450+
})
451+
.unwrap_or_else(|| {
452+
let mut configs = Vec::with_capacity(num_nodes);
453+
configs.resize_with(num_nodes, ValidatorConfig::default_for_test);
454+
configs
455+
});
420456

421457
// First validator is the bootstrap leader with the malicious broadcast logic.
422-
validator_configs.push(error_validator_config);
423-
validator_configs.resize_with(num_nodes, || ValidatorConfig {
424-
fixed_leader_schedule: fixed_leader_schedule.clone(),
425-
..ValidatorConfig::default_for_test()
426-
});
458+
validator_configs[0].broadcast_stage_type = faulty_node_type;
459+
for config in &mut validator_configs {
460+
config.fixed_leader_schedule = Some(fixed_leader_schedule.clone());
461+
}
427462

428463
let mut cluster_config = ClusterConfig {
429464
cluster_lamports: 10_000,

0 commit comments

Comments
 (0)