gemachain_program/message/
sanitized.rs

1use {
2    crate::{
3        fee_calculator::FeeCalculator,
4        hash::Hash,
5        instruction::{CompiledInstruction, Instruction},
6        message::{MappedAddresses, MappedMessage, Message, MessageHeader},
7        pubkey::Pubkey,
8        sanitize::{Sanitize, SanitizeError},
9        secp256k1_program,
10        serialize_utils::{append_slice, append_u16, append_u8},
11    },
12    bitflags::bitflags,
13    std::convert::TryFrom,
14    thiserror::Error,
15};
16
17/// Sanitized message of a transaction which includes a set of atomic
18/// instructions to be executed on-chain
19#[derive(Debug, Clone)]
20pub enum SanitizedMessage {
21    /// Sanitized legacy message
22    Legacy(Message),
23    /// Sanitized version #0 message with mapped addresses
24    V0(MappedMessage),
25}
26
27#[derive(PartialEq, Debug, Error, Eq, Clone)]
28pub enum SanitizeMessageError {
29    #[error("index out of bounds")]
30    IndexOutOfBounds,
31    #[error("value out of bounds")]
32    ValueOutOfBounds,
33    #[error("invalid value")]
34    InvalidValue,
35    #[error("duplicate account key")]
36    DuplicateAccountKey,
37}
38
39impl From<SanitizeError> for SanitizeMessageError {
40    fn from(err: SanitizeError) -> Self {
41        match err {
42            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
43            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
44            SanitizeError::InvalidValue => Self::InvalidValue,
45        }
46    }
47}
48
49impl TryFrom<Message> for SanitizedMessage {
50    type Error = SanitizeMessageError;
51    fn try_from(message: Message) -> Result<Self, Self::Error> {
52        message.sanitize()?;
53
54        let sanitized_msg = Self::Legacy(message);
55        if sanitized_msg.has_duplicates() {
56            return Err(SanitizeMessageError::DuplicateAccountKey);
57        }
58
59        Ok(sanitized_msg)
60    }
61}
62
63bitflags! {
64    struct InstructionsSysvarAccountMeta: u8 {
65        const NONE = 0b00000000;
66        const IS_SIGNER = 0b00000001;
67        const IS_WRITABLE = 0b00000010;
68    }
69}
70
71impl SanitizedMessage {
72    /// Return true if this message contains duplicate account keys
73    pub fn has_duplicates(&self) -> bool {
74        match self {
75            SanitizedMessage::Legacy(message) => message.has_duplicates(),
76            SanitizedMessage::V0(message) => message.has_duplicates(),
77        }
78    }
79
80    /// Message header which identifies the number of signer and writable or
81    /// readonly accounts
82    pub fn header(&self) -> &MessageHeader {
83        match self {
84            Self::Legacy(message) => &message.header,
85            Self::V0(mapped_msg) => &mapped_msg.message.header,
86        }
87    }
88
89    /// Returns a legacy message if this sanitized message wraps one
90    pub fn legacy_message(&self) -> Option<&Message> {
91        if let Self::Legacy(message) = &self {
92            Some(message)
93        } else {
94            None
95        }
96    }
97
98    /// Returns the fee payer for the transaction
99    pub fn fee_payer(&self) -> &Pubkey {
100        self.get_account_key(0)
101            .expect("sanitized message always has non-program fee payer at index 0")
102    }
103
104    /// The hash of a recent block, used for timing out a transaction
105    pub fn recent_blockhash(&self) -> &Hash {
106        match self {
107            Self::Legacy(message) => &message.recent_blockhash,
108            Self::V0(mapped_msg) => &mapped_msg.message.recent_blockhash,
109        }
110    }
111
112    /// Program instructions that will be executed in sequence and committed in
113    /// one atomic transaction if all succeed.
114    pub fn instructions(&self) -> &[CompiledInstruction] {
115        match self {
116            Self::Legacy(message) => &message.instructions,
117            Self::V0(mapped_msg) => &mapped_msg.message.instructions,
118        }
119    }
120
121    /// Program instructions iterator which includes each instruction's program
122    /// id.
123    pub fn program_instructions_iter(
124        &self,
125    ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
126        match self {
127            Self::Legacy(message) => message.instructions.iter(),
128            Self::V0(mapped_msg) => mapped_msg.message.instructions.iter(),
129        }
130        .map(move |ix| {
131            (
132                self.get_account_key(usize::from(ix.program_id_index))
133                    .expect("program id index is sanitized"),
134                ix,
135            )
136        })
137    }
138
139    /// Iterator of all account keys referenced in this message, included mapped keys.
140    pub fn account_keys_iter(&self) -> Box<dyn Iterator<Item = &Pubkey> + '_> {
141        match self {
142            Self::Legacy(message) => Box::new(message.account_keys.iter()),
143            Self::V0(mapped_msg) => Box::new(mapped_msg.account_keys_iter()),
144        }
145    }
146
147    /// Length of all account keys referenced in this message, included mapped keys.
148    pub fn account_keys_len(&self) -> usize {
149        match self {
150            Self::Legacy(message) => message.account_keys.len(),
151            Self::V0(mapped_msg) => mapped_msg.account_keys_len(),
152        }
153    }
154
155    /// Returns the address of the account at the specified index.
156    pub fn get_account_key(&self, index: usize) -> Option<&Pubkey> {
157        match self {
158            Self::Legacy(message) => message.account_keys.get(index),
159            Self::V0(message) => message.get_account_key(index),
160        }
161    }
162
163    /// Returns true if the account at the specified index is an input to some
164    /// program instruction in this message.
165    fn is_key_passed_to_program(&self, key_index: usize) -> bool {
166        if let Ok(key_index) = u8::try_from(key_index) {
167            self.instructions()
168                .iter()
169                .any(|ix| ix.accounts.contains(&key_index))
170        } else {
171            false
172        }
173    }
174
175    /// Returns true if the account at the specified index is invoked as a
176    /// program in this message.
177    pub fn is_invoked(&self, key_index: usize) -> bool {
178        match self {
179            Self::Legacy(message) => message.is_key_called_as_program(key_index),
180            Self::V0(message) => message.is_key_called_as_program(key_index),
181        }
182    }
183
184    /// Returns true if the account at the specified index is not invoked as a
185    /// program or, if invoked, is passed to a program.
186    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
187        !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
188    }
189
190    /// Returns true if the account at the specified index is writable by the
191    /// instructions in this message.
192    pub fn is_writable(&self, index: usize, demote_program_write_locks: bool) -> bool {
193        match self {
194            Self::Legacy(message) => message.is_writable(index, demote_program_write_locks),
195            Self::V0(message) => message.is_writable(index, demote_program_write_locks),
196        }
197    }
198
199    /// Returns true if the account at the specified index signed this
200    /// message.
201    pub fn is_signer(&self, index: usize) -> bool {
202        index < usize::from(self.header().num_required_signatures)
203    }
204
205    // First encode the number of instructions:
206    // [0..2 - num_instructions
207    //
208    // Then a table of offsets of where to find them in the data
209    //  3..2 * num_instructions table of instruction offsets
210    //
211    // Each instruction is then encoded as:
212    //   0..2 - num_accounts
213    //   2 - meta_byte -> (bit 0 signer, bit 1 is_writable)
214    //   3..35 - pubkey - 32 bytes
215    //   35..67 - program_id
216    //   67..69 - data len - u16
217    //   69..data_len - data
218    #[allow(clippy::integer_arithmetic)]
219    pub fn serialize_instructions(&self, demote_program_write_locks: bool) -> Vec<u8> {
220        // 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
221        let mut data = Vec::with_capacity(self.instructions().len() * (32 * 2));
222        append_u16(&mut data, self.instructions().len() as u16);
223        for _ in 0..self.instructions().len() {
224            append_u16(&mut data, 0);
225        }
226        for (i, (program_id, instruction)) in self.program_instructions_iter().enumerate() {
227            let start_instruction_offset = data.len() as u16;
228            let start = 2 + (2 * i);
229            data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
230            append_u16(&mut data, instruction.accounts.len() as u16);
231            for account_index in &instruction.accounts {
232                let account_index = *account_index as usize;
233                let is_signer = self.is_signer(account_index);
234                let is_writable = self.is_writable(account_index, demote_program_write_locks);
235                let mut account_meta = InstructionsSysvarAccountMeta::NONE;
236                if is_signer {
237                    account_meta |= InstructionsSysvarAccountMeta::IS_SIGNER;
238                }
239                if is_writable {
240                    account_meta |= InstructionsSysvarAccountMeta::IS_WRITABLE;
241                }
242                append_u8(&mut data, account_meta.bits());
243                append_slice(
244                    &mut data,
245                    self.get_account_key(account_index).unwrap().as_ref(),
246                );
247            }
248
249            append_slice(&mut data, program_id.as_ref());
250            append_u16(&mut data, instruction.data.len() as u16);
251            append_slice(&mut data, &instruction.data);
252        }
253        data
254    }
255
256    /// Return the mapped addresses for this message if it has any.
257    fn mapped_addresses(&self) -> Option<&MappedAddresses> {
258        match &self {
259            SanitizedMessage::V0(message) => Some(&message.mapped_addresses),
260            _ => None,
261        }
262    }
263
264    /// Return the number of readonly accounts loaded by this message.
265    pub fn num_readonly_accounts(&self) -> usize {
266        let mapped_readonly_addresses = self
267            .mapped_addresses()
268            .map(|keys| keys.readonly.len())
269            .unwrap_or_default();
270        mapped_readonly_addresses
271            .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
272            .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
273    }
274
275    fn try_position(&self, key: &Pubkey) -> Option<u8> {
276        u8::try_from(self.account_keys_iter().position(|k| k == key)?).ok()
277    }
278
279    /// Try to compile an instruction using the account keys in this message.
280    pub fn try_compile_instruction(&self, ix: &Instruction) -> Option<CompiledInstruction> {
281        let accounts: Vec<_> = ix
282            .accounts
283            .iter()
284            .map(|account_meta| self.try_position(&account_meta.pubkey))
285            .collect::<Option<_>>()?;
286
287        Some(CompiledInstruction {
288            program_id_index: self.try_position(&ix.program_id)?,
289            data: ix.data.clone(),
290            accounts,
291        })
292    }
293
294    /// Calculate the total fees for a transaction given a fee calculator
295    pub fn calculate_fee(&self, fee_calculator: &FeeCalculator) -> u64 {
296        let mut num_secp256k1_signatures: u64 = 0;
297        for (program_id, instruction) in self.program_instructions_iter() {
298            if secp256k1_program::check_id(program_id) {
299                if let Some(num_signatures) = instruction.data.get(0) {
300                    num_secp256k1_signatures =
301                        num_secp256k1_signatures.saturating_add(u64::from(*num_signatures));
302                }
303            }
304        }
305
306        fee_calculator.carats_per_signature.saturating_mul(
307            u64::from(self.header().num_required_signatures)
308                .saturating_add(num_secp256k1_signatures),
309        )
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use crate::{
317        instruction::{AccountMeta, Instruction},
318        message::v0,
319        secp256k1_program, system_instruction,
320    };
321
322    #[test]
323    fn test_try_from_message() {
324        let dupe_key = Pubkey::new_unique();
325        let legacy_message_with_dupes = Message {
326            header: MessageHeader {
327                num_required_signatures: 1,
328                ..MessageHeader::default()
329            },
330            account_keys: vec![dupe_key, dupe_key],
331            ..Message::default()
332        };
333
334        assert_eq!(
335            SanitizedMessage::try_from(legacy_message_with_dupes).err(),
336            Some(SanitizeMessageError::DuplicateAccountKey),
337        );
338
339        let legacy_message_with_no_signers = Message {
340            account_keys: vec![Pubkey::new_unique()],
341            ..Message::default()
342        };
343
344        assert_eq!(
345            SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
346            Some(SanitizeMessageError::IndexOutOfBounds),
347        );
348    }
349
350    #[test]
351    fn test_is_non_loader_key() {
352        let key0 = Pubkey::new_unique();
353        let key1 = Pubkey::new_unique();
354        let loader_key = Pubkey::new_unique();
355        let instructions = vec![
356            CompiledInstruction::new(1, &(), vec![0]),
357            CompiledInstruction::new(2, &(), vec![0, 1]),
358        ];
359
360        let message = SanitizedMessage::try_from(Message::new_with_compiled_instructions(
361            1,
362            0,
363            2,
364            vec![key0, key1, loader_key],
365            Hash::default(),
366            instructions,
367        ))
368        .unwrap();
369
370        assert!(message.is_non_loader_key(0));
371        assert!(message.is_non_loader_key(1));
372        assert!(!message.is_non_loader_key(2));
373    }
374
375    #[test]
376    fn test_num_readonly_accounts() {
377        let key0 = Pubkey::new_unique();
378        let key1 = Pubkey::new_unique();
379        let key2 = Pubkey::new_unique();
380        let key3 = Pubkey::new_unique();
381        let key4 = Pubkey::new_unique();
382        let key5 = Pubkey::new_unique();
383
384        let legacy_message = SanitizedMessage::try_from(Message {
385            header: MessageHeader {
386                num_required_signatures: 2,
387                num_readonly_signed_accounts: 1,
388                num_readonly_unsigned_accounts: 1,
389            },
390            account_keys: vec![key0, key1, key2, key3],
391            ..Message::default()
392        })
393        .unwrap();
394
395        assert_eq!(legacy_message.num_readonly_accounts(), 2);
396
397        let mapped_message = SanitizedMessage::V0(MappedMessage {
398            message: v0::Message {
399                header: MessageHeader {
400                    num_required_signatures: 2,
401                    num_readonly_signed_accounts: 1,
402                    num_readonly_unsigned_accounts: 1,
403                },
404                account_keys: vec![key0, key1, key2, key3],
405                ..v0::Message::default()
406            },
407            mapped_addresses: MappedAddresses {
408                writable: vec![key4],
409                readonly: vec![key5],
410            },
411        });
412
413        assert_eq!(mapped_message.num_readonly_accounts(), 3);
414    }
415
416    #[test]
417    #[allow(deprecated)]
418    fn test_serialize_instructions() {
419        let program_id0 = Pubkey::new_unique();
420        let program_id1 = Pubkey::new_unique();
421        let id0 = Pubkey::new_unique();
422        let id1 = Pubkey::new_unique();
423        let id2 = Pubkey::new_unique();
424        let id3 = Pubkey::new_unique();
425        let instructions = vec![
426            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
427            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
428            Instruction::new_with_bincode(
429                program_id1,
430                &0,
431                vec![AccountMeta::new_readonly(id2, false)],
432            ),
433            Instruction::new_with_bincode(
434                program_id1,
435                &0,
436                vec![AccountMeta::new_readonly(id3, true)],
437            ),
438        ];
439
440        let demote_program_write_locks = true;
441        let message = Message::new(&instructions, Some(&id1));
442        let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
443        let serialized = sanitized_message.serialize_instructions(demote_program_write_locks);
444
445        // assert that SanitizedMessage::serialize_instructions has the same behavior as the
446        // deprecated Message::serialize_instructions method
447        assert_eq!(serialized, message.serialize_instructions());
448
449        // assert that Message::deserialize_instruction is compatible with SanitizedMessage::serialize_instructions
450        for (i, instruction) in instructions.iter().enumerate() {
451            assert_eq!(
452                Message::deserialize_instruction(i, &serialized).unwrap(),
453                *instruction
454            );
455        }
456    }
457
458    #[test]
459    fn test_calculate_fee() {
460        // Default: no fee.
461        let message =
462            SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
463        assert_eq!(message.calculate_fee(&FeeCalculator::default()), 0);
464
465        // One signature, a fee.
466        assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 1);
467
468        // Two signatures, double the fee.
469        let key0 = Pubkey::new_unique();
470        let key1 = Pubkey::new_unique();
471        let ix0 = system_instruction::transfer(&key0, &key1, 1);
472        let ix1 = system_instruction::transfer(&key1, &key0, 1);
473        let message = SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&key0))).unwrap();
474        assert_eq!(message.calculate_fee(&FeeCalculator::new(2)), 4);
475    }
476
477    #[test]
478    fn test_try_compile_instruction() {
479        let key0 = Pubkey::new_unique();
480        let key1 = Pubkey::new_unique();
481        let key2 = Pubkey::new_unique();
482        let program_id = Pubkey::new_unique();
483
484        let valid_instruction = Instruction {
485            program_id,
486            accounts: vec![
487                AccountMeta::new_readonly(key0, false),
488                AccountMeta::new_readonly(key1, false),
489                AccountMeta::new_readonly(key2, false),
490            ],
491            data: vec![],
492        };
493
494        let invalid_program_id_instruction = Instruction {
495            program_id: Pubkey::new_unique(),
496            accounts: vec![
497                AccountMeta::new_readonly(key0, false),
498                AccountMeta::new_readonly(key1, false),
499                AccountMeta::new_readonly(key2, false),
500            ],
501            data: vec![],
502        };
503
504        let invalid_account_key_instruction = Instruction {
505            program_id: Pubkey::new_unique(),
506            accounts: vec![
507                AccountMeta::new_readonly(key0, false),
508                AccountMeta::new_readonly(key1, false),
509                AccountMeta::new_readonly(Pubkey::new_unique(), false),
510            ],
511            data: vec![],
512        };
513
514        let legacy_message = SanitizedMessage::try_from(Message {
515            header: MessageHeader {
516                num_required_signatures: 1,
517                num_readonly_signed_accounts: 0,
518                num_readonly_unsigned_accounts: 0,
519            },
520            account_keys: vec![key0, key1, key2, program_id],
521            ..Message::default()
522        })
523        .unwrap();
524
525        let mapped_message = SanitizedMessage::V0(MappedMessage {
526            message: v0::Message {
527                header: MessageHeader {
528                    num_required_signatures: 1,
529                    num_readonly_signed_accounts: 0,
530                    num_readonly_unsigned_accounts: 0,
531                },
532                account_keys: vec![key0, key1],
533                ..v0::Message::default()
534            },
535            mapped_addresses: MappedAddresses {
536                writable: vec![key2],
537                readonly: vec![program_id],
538            },
539        });
540
541        for message in vec![legacy_message, mapped_message] {
542            assert_eq!(
543                message.try_compile_instruction(&valid_instruction),
544                Some(CompiledInstruction {
545                    program_id_index: 3,
546                    accounts: vec![0, 1, 2],
547                    data: vec![],
548                })
549            );
550
551            assert!(message
552                .try_compile_instruction(&invalid_program_id_instruction)
553                .is_none());
554            assert!(message
555                .try_compile_instruction(&invalid_account_key_instruction)
556                .is_none());
557        }
558    }
559
560    #[test]
561    fn test_calculate_fee_secp256k1() {
562        let key0 = Pubkey::new_unique();
563        let key1 = Pubkey::new_unique();
564        let ix0 = system_instruction::transfer(&key0, &key1, 1);
565
566        let mut secp_instruction1 = Instruction {
567            program_id: secp256k1_program::id(),
568            accounts: vec![],
569            data: vec![],
570        };
571        let mut secp_instruction2 = Instruction {
572            program_id: secp256k1_program::id(),
573            accounts: vec![],
574            data: vec![1],
575        };
576
577        let message = SanitizedMessage::try_from(Message::new(
578            &[
579                ix0.clone(),
580                secp_instruction1.clone(),
581                secp_instruction2.clone(),
582            ],
583            Some(&key0),
584        ))
585        .unwrap();
586        assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 2);
587
588        secp_instruction1.data = vec![0];
589        secp_instruction2.data = vec![10];
590        let message = SanitizedMessage::try_from(Message::new(
591            &[ix0, secp_instruction1, secp_instruction2],
592            Some(&key0),
593        ))
594        .unwrap();
595        assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 11);
596    }
597}