spl_slashing/
processor.rs

1//! Program state processor
2
3use {
4    crate::{
5        duplicate_block_proof::DuplicateBlockProofData,
6        error::SlashingError,
7        instruction::{
8            decode_instruction_data, decode_instruction_type, DuplicateBlockProofInstructionData,
9            SlashingInstruction,
10        },
11        state::SlashingProofData,
12    },
13    solana_program::{
14        account_info::{next_account_info, AccountInfo},
15        clock::Slot,
16        entrypoint::ProgramResult,
17        msg,
18        program_error::ProgramError,
19        pubkey::Pubkey,
20        sysvar::{clock::Clock, epoch_schedule::EpochSchedule, Sysvar},
21    },
22};
23
24fn verify_proof_data<'a, T>(slot: Slot, pubkey: &Pubkey, proof_data: &'a [u8]) -> ProgramResult
25where
26    T: SlashingProofData<'a>,
27{
28    // Statue of limitations is 1 epoch
29    let clock = Clock::get()?;
30    let Some(elapsed) = clock.slot.checked_sub(slot) else {
31        return Err(ProgramError::ArithmeticOverflow);
32    };
33    let epoch_schedule = EpochSchedule::get()?;
34    if elapsed > epoch_schedule.slots_per_epoch {
35        return Err(SlashingError::ExceedsStatueOfLimitations.into());
36    }
37
38    let proof_data: T =
39        T::unpack(proof_data).map_err(|_| SlashingError::ShredDeserializationError)?;
40
41    SlashingProofData::verify_proof(proof_data, slot, pubkey)?;
42
43    // TODO: follow up PR will record this violation in context state account. just
44    // log for now.
45    msg!(
46        "{} violation verified in slot {}. This incident will be recorded",
47        T::PROOF_TYPE.violation_str(),
48        slot
49    );
50    Ok(())
51}
52
53/// Instruction processor
54pub fn process_instruction(
55    _program_id: &Pubkey,
56    accounts: &[AccountInfo],
57    input: &[u8],
58) -> ProgramResult {
59    let instruction_type = decode_instruction_type(input)?;
60    let account_info_iter = &mut accounts.iter();
61    let proof_data_info = next_account_info(account_info_iter);
62
63    match instruction_type {
64        SlashingInstruction::DuplicateBlockProof => {
65            let data = decode_instruction_data::<DuplicateBlockProofInstructionData>(input)?;
66            let proof_data = &proof_data_info?.data.borrow()[u64::from(data.offset) as usize..];
67            verify_proof_data::<DuplicateBlockProofData>(
68                data.slot.into(),
69                &data.node_pubkey,
70                proof_data,
71            )?;
72            Ok(())
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use {
80        super::verify_proof_data,
81        crate::{
82            duplicate_block_proof::DuplicateBlockProofData, error::SlashingError,
83            shred::tests::new_rand_data_shred,
84        },
85        rand::Rng,
86        solana_ledger::shred::Shredder,
87        solana_sdk::{
88            clock::{Clock, Slot, DEFAULT_SLOTS_PER_EPOCH},
89            epoch_schedule::EpochSchedule,
90            program_error::ProgramError,
91            signature::Keypair,
92            signer::Signer,
93        },
94        std::sync::{Arc, RwLock},
95    };
96
97    const SLOT: Slot = 53084024;
98    lazy_static::lazy_static! {
99        static ref CLOCK_SLOT: Arc<RwLock<Slot>> = Arc::new(RwLock::new(SLOT));
100    }
101
102    fn generate_proof_data(leader: Arc<Keypair>) -> Vec<u8> {
103        let mut rng = rand::thread_rng();
104        let (slot, parent_slot, reference_tick, version) = (SLOT, SLOT - 1, 0, 0);
105        let shredder = Shredder::new(slot, parent_slot, reference_tick, version).unwrap();
106        let next_shred_index = rng.gen_range(0..32_000);
107        let shred1 =
108            new_rand_data_shred(&mut rng, next_shred_index, &shredder, &leader, true, true);
109        let shred2 =
110            new_rand_data_shred(&mut rng, next_shred_index, &shredder, &leader, true, true);
111        let proof = DuplicateBlockProofData {
112            shred1: shred1.payload().as_slice(),
113            shred2: shred2.payload().as_slice(),
114        };
115        proof.pack()
116    }
117
118    #[test]
119    fn test_statue_of_limitations() {
120        *CLOCK_SLOT.write().unwrap() = SLOT + 5;
121        verify_with_clock().unwrap();
122
123        *CLOCK_SLOT.write().unwrap() = SLOT - 1;
124        assert_eq!(
125            verify_with_clock().unwrap_err(),
126            ProgramError::ArithmeticOverflow
127        );
128
129        *CLOCK_SLOT.write().unwrap() = SLOT + DEFAULT_SLOTS_PER_EPOCH + 1;
130        assert_eq!(
131            verify_with_clock().unwrap_err(),
132            SlashingError::ExceedsStatueOfLimitations.into()
133        );
134    }
135
136    fn verify_with_clock() -> Result<(), ProgramError> {
137        struct SyscallStubs {}
138        impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
139            fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 {
140                unsafe {
141                    let clock = Clock {
142                        slot: *CLOCK_SLOT.read().unwrap(),
143                        ..Clock::default()
144                    };
145                    *(var_addr as *mut _ as *mut Clock) = clock;
146                }
147                solana_program::entrypoint::SUCCESS
148            }
149
150            fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 {
151                unsafe {
152                    *(var_addr as *mut _ as *mut EpochSchedule) = EpochSchedule::default();
153                }
154                solana_program::entrypoint::SUCCESS
155            }
156        }
157
158        solana_sdk::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
159        let leader = Arc::new(Keypair::new());
160        verify_proof_data::<DuplicateBlockProofData>(
161            SLOT,
162            &leader.pubkey(),
163            &generate_proof_data(leader),
164        )
165    }
166}