solana_runtime_transaction/
signature_details.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// static account keys has max
use {
    agave_transaction_view::static_account_keys_frame::MAX_STATIC_ACCOUNTS_PER_PACKET as FILTER_SIZE,
    solana_pubkey::Pubkey, solana_svm_transaction::instruction::SVMInstruction,
};

pub struct PrecompileSignatureDetails {
    pub num_secp256k1_instruction_signatures: u64,
    pub num_ed25519_instruction_signatures: u64,
}

/// Get transaction signature details.
pub fn get_precompile_signature_details<'a>(
    instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
) -> PrecompileSignatureDetails {
    let mut filter = SignatureDetailsFilter::new();

    // Wrapping arithmetic is safe below because the maximum number of signatures
    // per instruction is 255, and the maximum number of instructions per transaction
    // is low enough that the sum of all signatures will not overflow a u64.
    let mut num_secp256k1_instruction_signatures: u64 = 0;
    let mut num_ed25519_instruction_signatures: u64 = 0;
    for (program_id, instruction) in instructions {
        let program_id_index = instruction.program_id_index;
        match filter.is_signature(program_id_index, program_id) {
            ProgramIdStatus::NotSignature => {}
            ProgramIdStatus::Secp256k1 => {
                num_secp256k1_instruction_signatures = num_secp256k1_instruction_signatures
                    .wrapping_add(get_num_signatures_in_instruction(&instruction));
            }
            ProgramIdStatus::Ed25519 => {
                num_ed25519_instruction_signatures = num_ed25519_instruction_signatures
                    .wrapping_add(get_num_signatures_in_instruction(&instruction));
            }
        }
    }

    PrecompileSignatureDetails {
        num_secp256k1_instruction_signatures,
        num_ed25519_instruction_signatures,
    }
}

#[inline]
fn get_num_signatures_in_instruction(instruction: &SVMInstruction) -> u64 {
    u64::from(instruction.data.first().copied().unwrap_or(0))
}

#[derive(Copy, Clone)]
enum ProgramIdStatus {
    NotSignature,
    Secp256k1,
    Ed25519,
}

struct SignatureDetailsFilter {
    // array of slots for all possible static and sanitized program_id_index,
    // each slot indicates if a program_id_index has not been checked, or is
    // already checked with result that can be reused.
    flags: [Option<ProgramIdStatus>; FILTER_SIZE as usize],
}

impl SignatureDetailsFilter {
    #[inline]
    fn new() -> Self {
        Self {
            flags: [None; FILTER_SIZE as usize],
        }
    }

    #[inline]
    fn is_signature(&mut self, index: u8, program_id: &Pubkey) -> ProgramIdStatus {
        let flag = &mut self.flags[usize::from(index)];
        match flag {
            Some(status) => *status,
            None => {
                *flag = Some(Self::check_program_id(program_id));
                *flag.as_ref().unwrap()
            }
        }
    }

    #[inline]
    fn check_program_id(program_id: &Pubkey) -> ProgramIdStatus {
        if program_id == &solana_sdk::secp256k1_program::ID {
            ProgramIdStatus::Secp256k1
        } else if program_id == &solana_sdk::ed25519_program::ID {
            ProgramIdStatus::Ed25519
        } else {
            ProgramIdStatus::NotSignature
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // simple convenience function so avoid having inconsistent program_id and program_id_index
    fn make_instruction<'a>(
        program_ids: &'a [Pubkey],
        program_id_index: u8,
        data: &'a [u8],
    ) -> (&'a Pubkey, SVMInstruction<'a>) {
        (
            &program_ids[program_id_index as usize],
            SVMInstruction {
                program_id_index,
                accounts: &[],
                data,
            },
        )
    }

    #[test]
    fn test_get_signature_details_no_instructions() {
        let instructions = std::iter::empty();
        let signature_details = get_precompile_signature_details(instructions);

        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
    }

    #[test]
    fn test_get_signature_details_no_sigs_unique() {
        let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
        let instructions = [
            make_instruction(&program_ids, 0, &[]),
            make_instruction(&program_ids, 1, &[]),
        ];

        let signature_details = get_precompile_signature_details(instructions.into_iter());
        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
    }

    #[test]
    fn test_get_signature_details_signatures_mixed() {
        let program_ids = [
            Pubkey::new_unique(),
            solana_sdk::secp256k1_program::ID,
            solana_sdk::ed25519_program::ID,
        ];
        let instructions = [
            make_instruction(&program_ids, 1, &[5]),
            make_instruction(&program_ids, 2, &[3]),
            make_instruction(&program_ids, 0, &[]),
            make_instruction(&program_ids, 2, &[2]),
            make_instruction(&program_ids, 1, &[1]),
            make_instruction(&program_ids, 0, &[]),
        ];

        let signature_details = get_precompile_signature_details(instructions.into_iter());
        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 6);
        assert_eq!(signature_details.num_ed25519_instruction_signatures, 5);
    }

    #[test]
    fn test_get_signature_details_missing_num_signatures() {
        let program_ids = [
            solana_sdk::secp256k1_program::ID,
            solana_sdk::ed25519_program::ID,
        ];
        let instructions = [
            make_instruction(&program_ids, 0, &[]),
            make_instruction(&program_ids, 1, &[]),
        ];

        let signature_details = get_precompile_signature_details(instructions.into_iter());
        assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
        assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
    }
}