spl_slashing/
instruction.rs

1//! Program instructions
2
3use {
4    crate::{error::SlashingError, id},
5    bytemuck::{Pod, Zeroable},
6    num_enum::{IntoPrimitive, TryFromPrimitive},
7    solana_program::{
8        clock::Slot,
9        instruction::{AccountMeta, Instruction},
10        program_error::ProgramError,
11        pubkey::Pubkey,
12    },
13    spl_pod::{
14        bytemuck::{pod_from_bytes, pod_get_packed_len},
15        primitives::PodU64,
16    },
17};
18
19/// Instructions supported by the program
20#[repr(u8)]
21#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
22pub enum SlashingInstruction {
23    /// Submit a slashable violation proof for `node_pubkey`, which indicates
24    /// that they submitted a duplicate block to the network
25    ///
26    ///
27    /// Accounts expected by this instruction:
28    /// 0. `[]` Proof account, must be previously initialized with the proof
29    ///    data.
30    ///
31    /// We expect the proof account to be properly sized as to hold a duplicate
32    /// block proof. See [`ProofType`] for sizing requirements.
33    ///
34    /// Deserializing the proof account from `offset` should result in a
35    /// [`DuplicateBlockProofData`]
36    ///
37    /// Data expected by this instruction:
38    ///   `DuplicateBlockProofInstructionData`
39    DuplicateBlockProof,
40}
41
42/// Data expected by
43/// `SlashingInstruction::DuplicateBlockProof`
44#[repr(C)]
45#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
46pub struct DuplicateBlockProofInstructionData {
47    /// Offset into the proof account to begin reading, expressed as `u64`
48    pub(crate) offset: PodU64,
49    /// Slot for which the violation occurred
50    pub(crate) slot: PodU64,
51    /// Identity pubkey of the Node that signed the duplicate block
52    pub(crate) node_pubkey: Pubkey,
53}
54
55/// Utility function for encoding instruction data
56pub(crate) fn encode_instruction<D: Pod>(
57    accounts: Vec<AccountMeta>,
58    instruction: SlashingInstruction,
59    instruction_data: &D,
60) -> Instruction {
61    let mut data = vec![u8::from(instruction)];
62    data.extend_from_slice(bytemuck::bytes_of(instruction_data));
63    Instruction {
64        program_id: id(),
65        accounts,
66        data,
67    }
68}
69
70/// Utility function for decoding just the instruction type
71pub(crate) fn decode_instruction_type(input: &[u8]) -> Result<SlashingInstruction, ProgramError> {
72    if input.is_empty() {
73        Err(ProgramError::InvalidInstructionData)
74    } else {
75        SlashingInstruction::try_from(input[0])
76            .map_err(|_| SlashingError::InvalidInstruction.into())
77    }
78}
79
80/// Utility function for decoding instruction data
81pub(crate) fn decode_instruction_data<T: Pod>(input_with_type: &[u8]) -> Result<&T, ProgramError> {
82    if input_with_type.len() != pod_get_packed_len::<T>().saturating_add(1) {
83        Err(ProgramError::InvalidInstructionData)
84    } else {
85        pod_from_bytes(&input_with_type[1..])
86    }
87}
88
89/// Create a `SlashingInstruction::DuplicateBlockProof` instruction
90pub fn duplicate_block_proof(
91    proof_account: &Pubkey,
92    offset: u64,
93    slot: Slot,
94    node_pubkey: Pubkey,
95) -> Instruction {
96    encode_instruction(
97        vec![AccountMeta::new_readonly(*proof_account, false)],
98        SlashingInstruction::DuplicateBlockProof,
99        &DuplicateBlockProofInstructionData {
100            offset: PodU64::from(offset),
101            slot: PodU64::from(slot),
102            node_pubkey,
103        },
104    )
105}
106
107#[cfg(test)]
108mod tests {
109    use {super::*, solana_program::program_error::ProgramError};
110
111    const TEST_BYTES: [u8; 8] = [42; 8];
112
113    #[test]
114    fn serialize_duplicate_block_proof() {
115        let offset = 34;
116        let slot = 42;
117        let node_pubkey = Pubkey::new_unique();
118        let instruction = duplicate_block_proof(&Pubkey::new_unique(), offset, slot, node_pubkey);
119        let mut expected = vec![0];
120        expected.extend_from_slice(&offset.to_le_bytes());
121        expected.extend_from_slice(&slot.to_le_bytes());
122        expected.extend_from_slice(&node_pubkey.to_bytes());
123        assert_eq!(instruction.data, expected);
124
125        assert_eq!(
126            SlashingInstruction::DuplicateBlockProof,
127            decode_instruction_type(&instruction.data).unwrap()
128        );
129        let instruction_data: &DuplicateBlockProofInstructionData =
130            decode_instruction_data(&instruction.data).unwrap();
131
132        assert_eq!(instruction_data.offset, offset.into());
133        assert_eq!(instruction_data.slot, slot.into());
134        assert_eq!(instruction_data.node_pubkey, node_pubkey);
135    }
136
137    #[test]
138    fn deserialize_invalid_instruction() {
139        let mut expected = vec![12];
140        expected.extend_from_slice(&TEST_BYTES);
141        let err: ProgramError = decode_instruction_type(&expected).unwrap_err();
142        assert_eq!(err, SlashingError::InvalidInstruction.into());
143    }
144}