use {
super::{
repair_handler::RepairHandler, repair_response::repair_response_packet_from_bytes,
standard_repair_handler::StandardRepairHandler,
},
log::info,
solana_clock::Slot,
solana_entry::entry::Entry,
solana_hash::Hash,
solana_keypair::Keypair,
solana_ledger::{
blockstore::Blockstore,
leader_schedule_cache::LeaderScheduleCache,
shred::{Nonce, ProcessShredsStats, ReedSolomonCache, Shred, Shredder},
},
solana_perf::packet::{Packet, PacketBatch, PacketBatchRecycler},
solana_signer::Signer,
std::{net::SocketAddr, sync::Arc},
};
#[derive(Copy, Clone, Debug, Default)]
pub struct MaliciousRepairConfig {
pub bad_shred_slot_frequency: Option<Slot>,
pub bad_shred_index_frequency: Option<u64>,
pub slot_range: Option<(Slot, Slot)>,
}
pub struct MaliciousRepairHandler {
blockstore: Arc<Blockstore>,
keypair: Arc<Keypair>,
leader_schedule_cache: Arc<LeaderScheduleCache>,
config: MaliciousRepairConfig,
reed_solomon_cache: ReedSolomonCache,
standard_repair_handler: StandardRepairHandler,
}
impl MaliciousRepairHandler {
pub fn new(
blockstore: Arc<Blockstore>,
keypair: Arc<Keypair>,
leader_schedule_cache: Arc<LeaderScheduleCache>,
config: MaliciousRepairConfig,
) -> Self {
Self {
standard_repair_handler: StandardRepairHandler::new(blockstore.clone()),
blockstore,
keypair,
leader_schedule_cache,
config,
reed_solomon_cache: ReedSolomonCache::default(),
}
}
fn should_respond_maliciously(&self, slot: Slot, shred_index: u64) -> bool {
if let Some((start, end)) = self.config.slot_range
&& (slot < start || slot > end)
{
return false;
}
let slot_matches = self
.config
.bad_shred_slot_frequency
.is_some_and(|freq| slot.is_multiple_of(freq));
let index_matches = self
.config
.bad_shred_index_frequency
.is_some_and(|freq| shred_index.is_multiple_of(freq));
match (
self.config.bad_shred_slot_frequency,
self.config.bad_shred_index_frequency,
) {
(Some(_), Some(_)) => slot_matches && index_matches,
(Some(_), None) => slot_matches,
(None, Some(_)) => index_matches,
(None, None) => false,
}
}
fn is_leader_for_slot(&self, slot: Slot) -> bool {
self.leader_schedule_cache
.slot_leader_at(slot, None)
.is_some_and(|leader| leader.id == self.keypair.pubkey())
}
fn generate_equivocating_shred(
&self,
original_shred: &Shred,
shred_index: u64,
) -> Option<Vec<u8>> {
let slot = original_shred.slot();
let parent_slot = original_shred.parent().ok()?;
let version = original_shred.version();
let reference_tick = 0u8;
let shredder = Shredder::new(slot, parent_slot, reference_tick, version).ok()?;
let fake_hash = Hash::new_unique();
let fake_entries = vec![Entry::new(&fake_hash, 1, vec![])];
let chained_merkle_root = original_shred.chained_merkle_root().ok()?;
let is_last_in_slot = original_shred.last_in_slot();
let shreds: Vec<Shred> = shredder
.make_merkle_shreds_from_entries(
&self.keypair,
&fake_entries,
is_last_in_slot,
chained_merkle_root,
shred_index as u32, 0, &self.reed_solomon_cache,
&mut ProcessShredsStats::default(),
)
.collect();
shreds
.into_iter()
.find(|s| s.is_data())
.map(|s| s.into_payload().to_vec())
}
}
impl RepairHandler for MaliciousRepairHandler {
fn blockstore(&self) -> &Blockstore {
&self.blockstore
}
fn repair_response_packet(
&self,
slot: Slot,
shred_index: u64,
dest: &SocketAddr,
nonce: Nonce,
) -> Option<Packet> {
let original_shred_bytes = self
.blockstore
.get_data_shred(slot, shred_index)
.expect("Blockstore could not get data shred")?;
if self.is_leader_for_slot(slot) && self.should_respond_maliciously(slot, shred_index) {
if let Ok(original_shred) =
Shred::new_from_serialized_shred(original_shred_bytes.clone())
&& let Some(equivocating_shred) =
self.generate_equivocating_shred(&original_shred, shred_index)
{
info!(
"Responding with equivocating shred in slot {slot} index {shred_index} to \
{dest}"
);
return repair_response_packet_from_bytes(equivocating_shred, dest, nonce);
}
}
repair_response_packet_from_bytes(original_shred_bytes, dest, nonce)
}
fn run_orphan(
&self,
recycler: &PacketBatchRecycler,
from_addr: &SocketAddr,
slot: Slot,
max_responses: usize,
nonce: Nonce,
) -> Option<PacketBatch> {
self.standard_repair_handler
.run_orphan(recycler, from_addr, slot, max_responses, nonce)
}
}