solana_runtime_transaction/
signature_details.rs

1// static account keys has max
2use {
3    agave_transaction_view::static_account_keys_frame::MAX_STATIC_ACCOUNTS_PER_PACKET as FILTER_SIZE,
4    solana_pubkey::Pubkey, solana_svm_transaction::instruction::SVMInstruction,
5};
6
7pub struct PrecompileSignatureDetails {
8    pub num_secp256k1_instruction_signatures: u64,
9    pub num_ed25519_instruction_signatures: u64,
10}
11
12/// Get transaction signature details.
13pub fn get_precompile_signature_details<'a>(
14    instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
15) -> PrecompileSignatureDetails {
16    let mut filter = SignatureDetailsFilter::new();
17
18    // Wrapping arithmetic is safe below because the maximum number of signatures
19    // per instruction is 255, and the maximum number of instructions per transaction
20    // is low enough that the sum of all signatures will not overflow a u64.
21    let mut num_secp256k1_instruction_signatures: u64 = 0;
22    let mut num_ed25519_instruction_signatures: u64 = 0;
23    for (program_id, instruction) in instructions {
24        let program_id_index = instruction.program_id_index;
25        match filter.is_signature(program_id_index, program_id) {
26            ProgramIdStatus::NotSignature => {}
27            ProgramIdStatus::Secp256k1 => {
28                num_secp256k1_instruction_signatures = num_secp256k1_instruction_signatures
29                    .wrapping_add(get_num_signatures_in_instruction(&instruction));
30            }
31            ProgramIdStatus::Ed25519 => {
32                num_ed25519_instruction_signatures = num_ed25519_instruction_signatures
33                    .wrapping_add(get_num_signatures_in_instruction(&instruction));
34            }
35        }
36    }
37
38    PrecompileSignatureDetails {
39        num_secp256k1_instruction_signatures,
40        num_ed25519_instruction_signatures,
41    }
42}
43
44#[inline]
45fn get_num_signatures_in_instruction(instruction: &SVMInstruction) -> u64 {
46    u64::from(instruction.data.first().copied().unwrap_or(0))
47}
48
49#[derive(Copy, Clone)]
50enum ProgramIdStatus {
51    NotSignature,
52    Secp256k1,
53    Ed25519,
54}
55
56struct SignatureDetailsFilter {
57    // array of slots for all possible static and sanitized program_id_index,
58    // each slot indicates if a program_id_index has not been checked, or is
59    // already checked with result that can be reused.
60    flags: [Option<ProgramIdStatus>; FILTER_SIZE as usize],
61}
62
63impl SignatureDetailsFilter {
64    #[inline]
65    fn new() -> Self {
66        Self {
67            flags: [None; FILTER_SIZE as usize],
68        }
69    }
70
71    #[inline]
72    fn is_signature(&mut self, index: u8, program_id: &Pubkey) -> ProgramIdStatus {
73        let flag = &mut self.flags[usize::from(index)];
74        match flag {
75            Some(status) => *status,
76            None => {
77                *flag = Some(Self::check_program_id(program_id));
78                *flag.as_ref().unwrap()
79            }
80        }
81    }
82
83    #[inline]
84    fn check_program_id(program_id: &Pubkey) -> ProgramIdStatus {
85        if program_id == &solana_sdk::secp256k1_program::ID {
86            ProgramIdStatus::Secp256k1
87        } else if program_id == &solana_sdk::ed25519_program::ID {
88            ProgramIdStatus::Ed25519
89        } else {
90            ProgramIdStatus::NotSignature
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    // simple convenience function so avoid having inconsistent program_id and program_id_index
100    fn make_instruction<'a>(
101        program_ids: &'a [Pubkey],
102        program_id_index: u8,
103        data: &'a [u8],
104    ) -> (&'a Pubkey, SVMInstruction<'a>) {
105        (
106            &program_ids[program_id_index as usize],
107            SVMInstruction {
108                program_id_index,
109                accounts: &[],
110                data,
111            },
112        )
113    }
114
115    #[test]
116    fn test_get_signature_details_no_instructions() {
117        let instructions = std::iter::empty();
118        let signature_details = get_precompile_signature_details(instructions);
119
120        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
121        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
122    }
123
124    #[test]
125    fn test_get_signature_details_no_sigs_unique() {
126        let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
127        let instructions = [
128            make_instruction(&program_ids, 0, &[]),
129            make_instruction(&program_ids, 1, &[]),
130        ];
131
132        let signature_details = get_precompile_signature_details(instructions.into_iter());
133        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
134        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
135    }
136
137    #[test]
138    fn test_get_signature_details_signatures_mixed() {
139        let program_ids = [
140            Pubkey::new_unique(),
141            solana_sdk::secp256k1_program::ID,
142            solana_sdk::ed25519_program::ID,
143        ];
144        let instructions = [
145            make_instruction(&program_ids, 1, &[5]),
146            make_instruction(&program_ids, 2, &[3]),
147            make_instruction(&program_ids, 0, &[]),
148            make_instruction(&program_ids, 2, &[2]),
149            make_instruction(&program_ids, 1, &[1]),
150            make_instruction(&program_ids, 0, &[]),
151        ];
152
153        let signature_details = get_precompile_signature_details(instructions.into_iter());
154        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 6);
155        assert_eq!(signature_details.num_ed25519_instruction_signatures, 5);
156    }
157
158    #[test]
159    fn test_get_signature_details_missing_num_signatures() {
160        let program_ids = [
161            solana_sdk::secp256k1_program::ID,
162            solana_sdk::ed25519_program::ID,
163        ];
164        let instructions = [
165            make_instruction(&program_ids, 0, &[]),
166            make_instruction(&program_ids, 1, &[]),
167        ];
168
169        let signature_details = get_precompile_signature_details(instructions.into_iter());
170        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
171        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
172    }
173}