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