poseidon_client/transactions/
message.rs

1use crate::{
2    CompiledInstruction, MessageBuilder, PoseidonError, PoseidonResult, PublicKey, RecentBlockHash,
3};
4use borsh::{BorshDeserialize, BorshSerialize};
5use core::fmt;
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8
9#[derive(PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, Deserialize, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Message {
12    /// The message header, identifying signed and read-only `account_keys`
13    /// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
14    pub header: MessageHeader,
15
16    /// All the account keys used by this transaction
17    #[serde(with = "short_vec")]
18    pub account_keys: Vec<PublicKey>,
19
20    /// The id of a recent ledger entry.
21    pub recent_blockhash: RecentBlockHash,
22
23    /// Programs that will be executed in sequence and committed in one atomic transaction if all
24    /// succeed.
25    #[serde(with = "short_vec")]
26    pub instructions: Vec<CompiledInstruction>,
27}
28
29impl Default for Message {
30    fn default() -> Self {
31        Message::new()
32    }
33}
34
35impl Message {
36    pub fn new() -> Self {
37        Self {
38            header: MessageHeader::default(),
39            account_keys: Vec::default(),
40            recent_blockhash: RecentBlockHash::default(),
41            instructions: Vec::default(),
42        }
43    }
44
45    pub fn add_recent_blockhash(&mut self, blockhash: RecentBlockHash) -> &mut Self {
46        self.recent_blockhash = blockhash;
47
48        self
49    }
50
51    pub fn build(&mut self, message_builder: MessageBuilder) -> PoseidonResult<&mut Self> {
52        let mut all_keys = message_builder.signed_keys;
53        let num_required_signatures = all_keys.len() as u8;
54        all_keys.extend(&message_builder.unsigned_keys);
55        self.header.num_required_signatures = num_required_signatures;
56        self.header.num_readonly_unsigned_accounts = message_builder.num_readonly_unsigned_accounts;
57        self.header.num_readonly_signed_accounts = message_builder.num_readonly_signed_accounts;
58        self.account_keys = all_keys.into_iter().unique().collect_vec();
59        message_builder
60            .instructions
61            .iter()
62            .try_for_each(|instruction| {
63                let program_id_index = match self
64                    .account_keys
65                    .iter()
66                    .enumerate()
67                    .find(|(_, account)| *account == &instruction.program_id)
68                {
69                    Some((index, _)) => index as u8,
70                    None => return Err(PoseidonError::ProgramIdNotFound),
71                };
72
73                // FIXME make this more efficient. do it in one loop or flat_map
74                let account_indexes = message_builder
75                    .instructions
76                    .iter()
77                    .map(|instruction| {
78                        instruction
79                            .accounts
80                            .iter()
81                            .map(|account_meta| {
82                                match self
83                                    .account_keys
84                                    .iter()
85                                    .enumerate()
86                                    .find(|(_, public_key)| *public_key == &account_meta.pubkey)
87                                {
88                                    Some((index, _)) => Ok(index as u8),
89                                    None => Err(PoseidonError::PublicKeyNotFoundInMessageAccounts),
90                                }
91                            })
92                            .collect::<Result<Vec<u8>, PoseidonError>>()
93                            .map_err(|_| PoseidonError::AccountIndexNotFoundInMessageAccounts)
94                    })
95                    .collect::<Result<Vec<Vec<u8>>, PoseidonError>>()
96                    .map_err(|_| PoseidonError::AccountIndexNotFoundInMessageAccounts)?;
97
98                let account_indexes: Vec<u8> = account_indexes.into_iter().flatten().collect();
99
100                self.instructions.push(CompiledInstruction {
101                    program_id_index,
102                    accounts: account_indexes,
103                    data: instruction.data.clone(),
104                });
105
106                Ok(())
107            })?;
108
109        Ok(self)
110    }
111
112    pub fn to_bytes(&self) -> PoseidonResult<Vec<u8>> {
113        Ok(bincode::serialize(&self)?)
114    }
115}
116
117impl fmt::Debug for Message {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        let account_keys: Vec<String> = self
120            .account_keys
121            .iter()
122            .map(|account_key| bs58::encode(account_key).into_string())
123            .collect();
124        f.debug_struct("Message")
125            .field("header", &self.header)
126            .field("account_keys", &account_keys)
127            .field(
128                "recent_blockhash",
129                &bs58::encode(&self.recent_blockhash).into_string(),
130            )
131            .field("instructions", &self.instructions)
132            .finish()
133    }
134}
135
136#[derive(
137    Default,
138    Debug,
139    PartialEq,
140    Eq,
141    PartialOrd,
142    Ord,
143    Copy,
144    Clone,
145    Serialize,
146    Deserialize,
147    BorshSerialize,
148    BorshDeserialize,
149)]
150#[serde(rename_all = "camelCase")]
151pub struct MessageHeader {
152    /// The number of signatures required for this message to be considered valid. The
153    /// signatures must match the first `num_required_signatures` of `account_keys`.
154    /// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
155    pub num_required_signatures: u8,
156
157    /// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
158    /// may process multiple transactions that load read-only accounts within a single PoH entry,
159    /// but are not permitted to credit or debit lamports or modify account data. Transactions
160    /// targeting the same read-write account are evaluated sequentially.
161    pub num_readonly_signed_accounts: u8,
162
163    /// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
164    pub num_readonly_unsigned_accounts: u8,
165}