Skip to main content

laserstream_core_proto/
lib.rs

1#![allow(clippy::large_enum_variant)]
2
3pub mod geyser {
4    #![allow(clippy::clone_on_ref_ptr)]
5    #![allow(clippy::missing_const_for_fn)]
6
7    #[cfg(feature = "tonic")]
8    include!(concat!(env!("OUT_DIR"), "/geyser.rs"));
9    #[cfg(not(feature = "tonic"))]
10    include!(concat!(env!("OUT_DIR"), "/no-tonic/geyser.rs"));
11}
12
13pub mod solana {
14    #![allow(clippy::missing_const_for_fn)]
15
16    pub mod storage {
17        pub mod confirmed_block {
18            #[cfg(feature = "tonic")]
19            include!(concat!(
20                env!("OUT_DIR"),
21                "/solana.storage.confirmed_block.rs"
22            ));
23            #[cfg(not(feature = "tonic"))]
24            include!(concat!(
25                env!("OUT_DIR"),
26                "/no-tonic/solana.storage.confirmed_block.rs"
27            ));
28        }
29    }
30}
31
32pub mod cuckoo;
33
34pub mod prelude {
35    pub use super::{geyser::*, solana::storage::confirmed_block::*};
36}
37
38#[cfg(feature = "tonic")]
39pub use tonic;
40pub use {prost, prost_types};
41
42#[cfg(feature = "plugin")]
43pub mod plugin;
44
45#[cfg(feature = "convert")]
46pub mod convert_to {
47    use {
48        super::prelude as proto,
49        solana_clock::UnixTimestamp,
50        solana_message::{
51            compiled_instruction::CompiledInstruction,
52            v0::{LoadedMessage, MessageAddressTableLookup},
53            LegacyMessage, MessageHeader, SanitizedMessage,
54        },
55        solana_pubkey::Pubkey,
56        solana_signature::Signature,
57        solana_transaction::sanitized::SanitizedTransaction,
58        solana_transaction_context::transaction::TransactionReturnData,
59        solana_transaction_error::TransactionError,
60        solana_transaction_status::{
61            InnerInstruction, InnerInstructions, Reward, RewardType, TransactionStatusMeta,
62            TransactionTokenBalance,
63        },
64    };
65
66    pub fn create_transaction(tx: &SanitizedTransaction) -> proto::Transaction {
67        proto::Transaction {
68            signatures: tx
69                .signatures()
70                .iter()
71                .map(|signature| <Signature as AsRef<[u8]>>::as_ref(signature).into())
72                .collect(),
73            message: Some(create_message(tx.message())),
74        }
75    }
76
77    pub fn create_message(message: &SanitizedMessage) -> proto::Message {
78        match message {
79            SanitizedMessage::Legacy(LegacyMessage { message, .. }) => proto::Message {
80                header: Some(create_header(&message.header)),
81                account_keys: create_pubkeys(&message.account_keys),
82                recent_blockhash: message.recent_blockhash.to_bytes().into(),
83                instructions: create_instructions(&message.instructions),
84                versioned: false,
85                address_table_lookups: vec![],
86                config: None,
87            },
88            SanitizedMessage::V0(LoadedMessage { message, .. }) => proto::Message {
89                header: Some(create_header(&message.header)),
90                account_keys: create_pubkeys(&message.account_keys),
91                recent_blockhash: message.recent_blockhash.to_bytes().into(),
92                instructions: create_instructions(&message.instructions),
93                versioned: true,
94                address_table_lookups: create_lookups(&message.address_table_lookups),
95                config: None,
96            },
97            // V1 messages (agave 4.1): 4KB transactions with no address lookup
98            // tables; the recent blockhash is carried in `lifetime_specifier`.
99            SanitizedMessage::V1(cached) => proto::Message {
100                header: Some(create_header(&cached.message.header)),
101                account_keys: create_pubkeys(&cached.message.account_keys),
102                recent_blockhash: cached.message.lifetime_specifier.to_bytes().into(),
103                instructions: create_instructions(&cached.message.instructions),
104                versioned: true,
105                address_table_lookups: vec![],
106                config: Some(proto::TransactionConfig {
107                    priority_fee: cached.message.config.priority_fee,
108                    compute_unit_limit: cached.message.config.compute_unit_limit,
109                    loaded_accounts_data_size_limit: cached
110                        .message
111                        .config
112                        .loaded_accounts_data_size_limit,
113                    heap_size: cached.message.config.heap_size,
114                }),
115            },
116        }
117    }
118
119    pub const fn create_header(header: &MessageHeader) -> proto::MessageHeader {
120        proto::MessageHeader {
121            num_required_signatures: header.num_required_signatures as u32,
122            num_readonly_signed_accounts: header.num_readonly_signed_accounts as u32,
123            num_readonly_unsigned_accounts: header.num_readonly_unsigned_accounts as u32,
124        }
125    }
126
127    pub fn create_pubkeys(pubkeys: &[Pubkey]) -> Vec<Vec<u8>> {
128        pubkeys
129            .iter()
130            .map(|key| <Pubkey as AsRef<[u8]>>::as_ref(key).into())
131            .collect()
132    }
133
134    pub fn create_instructions(ixs: &[CompiledInstruction]) -> Vec<proto::CompiledInstruction> {
135        ixs.iter().map(create_instruction).collect()
136    }
137
138    pub fn create_instruction(ix: &CompiledInstruction) -> proto::CompiledInstruction {
139        proto::CompiledInstruction {
140            program_id_index: ix.program_id_index as u32,
141            accounts: ix.accounts.clone(),
142            data: ix.data.clone(),
143        }
144    }
145
146    pub fn create_lookups(
147        lookups: &[MessageAddressTableLookup],
148    ) -> Vec<proto::MessageAddressTableLookup> {
149        lookups.iter().map(create_lookup).collect()
150    }
151
152    pub fn create_lookup(lookup: &MessageAddressTableLookup) -> proto::MessageAddressTableLookup {
153        proto::MessageAddressTableLookup {
154            account_key: <Pubkey as AsRef<[u8]>>::as_ref(&lookup.account_key).into(),
155            writable_indexes: lookup.writable_indexes.clone(),
156            readonly_indexes: lookup.readonly_indexes.clone(),
157        }
158    }
159
160    pub fn create_transaction_meta(meta: &TransactionStatusMeta) -> proto::TransactionStatusMeta {
161        let TransactionStatusMeta {
162            status,
163            fee,
164            pre_balances,
165            post_balances,
166            inner_instructions,
167            log_messages,
168            pre_token_balances,
169            post_token_balances,
170            rewards,
171            loaded_addresses,
172            return_data,
173            compute_units_consumed,
174            cost_units,
175        } = meta;
176        let err = create_transaction_error(status);
177        let inner_instructions_none = inner_instructions.is_none();
178        let inner_instructions = inner_instructions
179            .as_deref()
180            .map(create_inner_instructions_vec)
181            .unwrap_or_default();
182        let log_messages_none = log_messages.is_none();
183        let log_messages = log_messages.clone().unwrap_or_default();
184        let pre_token_balances = pre_token_balances
185            .as_deref()
186            .map(create_token_balances)
187            .unwrap_or_default();
188        let post_token_balances = post_token_balances
189            .as_deref()
190            .map(create_token_balances)
191            .unwrap_or_default();
192        let rewards = rewards.as_deref().map(create_rewards).unwrap_or_default();
193        let loaded_writable_addresses = create_pubkeys(&loaded_addresses.writable);
194        let loaded_readonly_addresses = create_pubkeys(&loaded_addresses.readonly);
195
196        proto::TransactionStatusMeta {
197            err,
198            fee: *fee,
199            pre_balances: pre_balances.clone(),
200            post_balances: post_balances.clone(),
201            inner_instructions,
202            inner_instructions_none,
203            log_messages,
204            log_messages_none,
205            pre_token_balances,
206            post_token_balances,
207            rewards,
208            loaded_writable_addresses,
209            loaded_readonly_addresses,
210            return_data: return_data.as_ref().map(create_return_data),
211            return_data_none: return_data.is_none(),
212            compute_units_consumed: *compute_units_consumed,
213            cost_units: *cost_units,
214        }
215    }
216
217    pub fn create_transaction_error(
218        status: &Result<(), TransactionError>,
219    ) -> Option<proto::TransactionError> {
220        match status {
221            Ok(()) => None,
222            Err(err) => Some(proto::TransactionError {
223                err: bincode::serialize(&err).expect("transaction error to serialize to bytes"),
224            }),
225        }
226    }
227
228    pub fn create_inner_instructions_vec(
229        ixs: &[InnerInstructions],
230    ) -> Vec<proto::InnerInstructions> {
231        ixs.iter().map(create_inner_instructions).collect()
232    }
233
234    pub fn create_inner_instructions(instructions: &InnerInstructions) -> proto::InnerInstructions {
235        proto::InnerInstructions {
236            index: instructions.index as u32,
237            instructions: create_inner_instruction_vec(&instructions.instructions),
238        }
239    }
240
241    pub fn create_inner_instruction_vec(ixs: &[InnerInstruction]) -> Vec<proto::InnerInstruction> {
242        ixs.iter().map(create_inner_instruction).collect()
243    }
244
245    pub fn create_inner_instruction(instruction: &InnerInstruction) -> proto::InnerInstruction {
246        proto::InnerInstruction {
247            program_id_index: instruction.instruction.program_id_index as u32,
248            accounts: instruction.instruction.accounts.clone(),
249            data: instruction.instruction.data.clone(),
250            stack_height: instruction.stack_height,
251        }
252    }
253
254    pub fn create_token_balances(balances: &[TransactionTokenBalance]) -> Vec<proto::TokenBalance> {
255        balances.iter().map(create_token_balance).collect()
256    }
257
258    pub fn create_token_balance(balance: &TransactionTokenBalance) -> proto::TokenBalance {
259        proto::TokenBalance {
260            account_index: balance.account_index as u32,
261            mint: balance.mint.clone(),
262            ui_token_amount: Some(proto::UiTokenAmount {
263                ui_amount: balance.ui_token_amount.ui_amount.unwrap_or_default(),
264                decimals: balance.ui_token_amount.decimals as u32,
265                amount: balance.ui_token_amount.amount.clone(),
266                ui_amount_string: balance.ui_token_amount.ui_amount_string.clone(),
267            }),
268            owner: balance.owner.clone(),
269            program_id: balance.program_id.clone(),
270        }
271    }
272
273    pub fn create_rewards_obj(rewards: &[Reward], num_partitions: Option<u64>) -> proto::Rewards {
274        proto::Rewards {
275            rewards: create_rewards(rewards),
276            num_partitions: num_partitions.map(create_num_partitions),
277        }
278    }
279
280    pub fn create_rewards(rewards: &[Reward]) -> Vec<proto::Reward> {
281        rewards.iter().map(create_reward).collect()
282    }
283
284    pub fn create_reward(reward: &Reward) -> proto::Reward {
285        proto::Reward {
286            pubkey: reward.pubkey.clone(),
287            lamports: reward.lamports,
288            post_balance: reward.post_balance,
289            reward_type: create_reward_type(reward.reward_type) as i32,
290            commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(),
291            commission_bps: reward
292                .commission_bps
293                .map(|c| c.to_string())
294                .unwrap_or_default(),
295        }
296    }
297
298    pub const fn create_reward_type(reward_type: Option<RewardType>) -> proto::RewardType {
299        match reward_type {
300            None => proto::RewardType::Unspecified,
301            Some(RewardType::Fee) => proto::RewardType::Fee,
302            Some(RewardType::Rent) => proto::RewardType::Rent,
303            Some(RewardType::Staking) => proto::RewardType::Staking,
304            Some(RewardType::Voting) => proto::RewardType::Voting,
305            Some(RewardType::DeactivatedStake) => proto::RewardType::DeactivatedStake,
306        }
307    }
308
309    pub const fn create_num_partitions(num_partitions: u64) -> proto::NumPartitions {
310        proto::NumPartitions { num_partitions }
311    }
312
313    pub fn create_return_data(return_data: &TransactionReturnData) -> proto::ReturnData {
314        proto::ReturnData {
315            program_id: return_data.program_id.to_bytes().into(),
316            data: return_data.data.clone(),
317        }
318    }
319
320    pub const fn create_block_height(block_height: u64) -> proto::BlockHeight {
321        proto::BlockHeight { block_height }
322    }
323
324    pub const fn create_timestamp(timestamp: UnixTimestamp) -> proto::UnixTimestamp {
325        proto::UnixTimestamp { timestamp }
326    }
327}
328
329#[cfg(feature = "convert")]
330pub mod convert_from {
331    use {
332        super::prelude as proto,
333        solana_account::Account,
334        solana_account_decoder::parse_token::UiTokenAmount,
335        solana_hash::{Hash, HASH_BYTES},
336        solana_message::{
337            compiled_instruction::CompiledInstruction,
338            v0::{LoadedAddresses, Message as MessageV0, MessageAddressTableLookup},
339            v1::{Message as MessageV1, TransactionConfig},
340            Message, MessageHeader, VersionedMessage,
341        },
342        solana_pubkey::Pubkey,
343        solana_signature::Signature,
344        solana_transaction::versioned::VersionedTransaction,
345        solana_transaction_context::transaction::TransactionReturnData,
346        solana_transaction_error::TransactionError,
347        solana_transaction_status::{
348            ConfirmedBlock, InnerInstruction, InnerInstructions, Reward, RewardType,
349            RewardsAndNumPartitions, TransactionStatusMeta, TransactionTokenBalance,
350            TransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
351        },
352    };
353
354    type CreateResult<T> = Result<T, &'static str>;
355
356    pub fn create_block(block: proto::SubscribeUpdateBlock) -> CreateResult<ConfirmedBlock> {
357        let mut transactions = vec![];
358        for tx in block.transactions {
359            transactions.push(create_tx_with_meta(tx)?);
360        }
361
362        let block_rewards = block.rewards.ok_or("failed to get rewards")?;
363        let mut rewards = vec![];
364        for reward in block_rewards.rewards {
365            rewards.push(create_reward(reward)?);
366        }
367
368        Ok(ConfirmedBlock {
369            previous_blockhash: block.parent_blockhash,
370            blockhash: block.blockhash,
371            parent_slot: block.parent_slot,
372            transactions,
373            rewards,
374            num_partitions: block_rewards.num_partitions.map(|msg| msg.num_partitions),
375            block_time: Some(
376                block
377                    .block_time
378                    .map(|wrapper| wrapper.timestamp)
379                    .ok_or("failed to get block_time")?,
380            ),
381            block_height: Some(
382                block
383                    .block_height
384                    .map(|wrapper| wrapper.block_height)
385                    .ok_or("failed to get block_height")?,
386            ),
387        })
388    }
389
390    pub fn create_tx_with_meta(
391        tx: proto::SubscribeUpdateTransactionInfo,
392    ) -> CreateResult<TransactionWithStatusMeta> {
393        let meta = tx.meta.ok_or("failed to get transaction meta")?;
394        let tx = tx
395            .transaction
396            .ok_or("failed to get transaction transaction")?;
397
398        Ok(TransactionWithStatusMeta::Complete(
399            VersionedTransactionWithStatusMeta {
400                transaction: create_tx_versioned(tx)?,
401                meta: create_tx_meta(meta)?,
402            },
403        ))
404    }
405
406    pub fn create_tx_versioned(tx: proto::Transaction) -> CreateResult<VersionedTransaction> {
407        let mut signatures = Vec::with_capacity(tx.signatures.len());
408        for signature in tx.signatures {
409            signatures.push(match Signature::try_from(signature.as_slice()) {
410                Ok(signature) => signature,
411                Err(_error) => return Err("failed to parse Signature"),
412            });
413        }
414
415        Ok(VersionedTransaction {
416            signatures,
417            message: create_message(tx.message.ok_or("failed to get message")?)?,
418        })
419    }
420
421    pub fn create_message(message: proto::Message) -> CreateResult<VersionedMessage> {
422        let header = message.header.ok_or("failed to get MessageHeader")?;
423        let header = MessageHeader {
424            num_required_signatures: header
425                .num_required_signatures
426                .try_into()
427                .map_err(|_| "failed to parse num_required_signatures")?,
428            num_readonly_signed_accounts: header
429                .num_readonly_signed_accounts
430                .try_into()
431                .map_err(|_| "failed to parse num_readonly_signed_accounts")?,
432            num_readonly_unsigned_accounts: header
433                .num_readonly_unsigned_accounts
434                .try_into()
435                .map_err(|_| "failed to parse num_readonly_unsigned_accounts")?,
436        };
437
438        let Ok(blockhash) = <[u8; HASH_BYTES]>::try_from(message.recent_blockhash.as_slice())
439        else {
440            return Err("failed to parse hash");
441        };
442        let recent_blockhash = Hash::new_from_array(blockhash);
443
444        // `config` is set only for V1 messages, whose lifetime specifier is carried in
445        // `recent_blockhash` and which have no address table lookups. `versioned` is
446        // true for both V0 and V1, so it cannot tell them apart on its own.
447        if let Some(config) = message.config {
448            return Ok(VersionedMessage::V1(MessageV1 {
449                header,
450                config: TransactionConfig {
451                    priority_fee: config.priority_fee,
452                    compute_unit_limit: config.compute_unit_limit,
453                    loaded_accounts_data_size_limit: config.loaded_accounts_data_size_limit,
454                    heap_size: config.heap_size,
455                },
456                lifetime_specifier: recent_blockhash,
457                account_keys: create_pubkey_vec(message.account_keys)?,
458                instructions: create_message_instructions(message.instructions)?,
459            }));
460        }
461
462        Ok(if message.versioned {
463            let mut address_table_lookups = Vec::with_capacity(message.address_table_lookups.len());
464            for table in message.address_table_lookups {
465                address_table_lookups.push(MessageAddressTableLookup {
466                    account_key: Pubkey::try_from(table.account_key.as_slice())
467                        .map_err(|_| "failed to parse Pubkey")?,
468                    writable_indexes: table.writable_indexes,
469                    readonly_indexes: table.readonly_indexes,
470                });
471            }
472
473            VersionedMessage::V0(MessageV0 {
474                header,
475                account_keys: create_pubkey_vec(message.account_keys)?,
476                recent_blockhash,
477                instructions: create_message_instructions(message.instructions)?,
478                address_table_lookups,
479            })
480        } else {
481            VersionedMessage::Legacy(Message {
482                header,
483                account_keys: create_pubkey_vec(message.account_keys)?,
484                recent_blockhash,
485                instructions: create_message_instructions(message.instructions)?,
486            })
487        })
488    }
489
490    pub fn create_message_instructions(
491        ixs: Vec<proto::CompiledInstruction>,
492    ) -> CreateResult<Vec<CompiledInstruction>> {
493        ixs.into_iter().map(create_message_instruction).collect()
494    }
495
496    pub fn create_message_instruction(
497        ix: proto::CompiledInstruction,
498    ) -> CreateResult<CompiledInstruction> {
499        Ok(CompiledInstruction {
500            program_id_index: ix
501                .program_id_index
502                .try_into()
503                .map_err(|_| "failed to decode CompiledInstruction.program_id_index)")?,
504            accounts: ix.accounts,
505            data: ix.data,
506        })
507    }
508
509    pub fn create_tx_meta(
510        meta: proto::TransactionStatusMeta,
511    ) -> CreateResult<TransactionStatusMeta> {
512        let meta_status = match create_tx_error(meta.err.as_ref())? {
513            Some(err) => Err(err),
514            None => Ok(()),
515        };
516        let meta_rewards = meta
517            .rewards
518            .into_iter()
519            .map(create_reward)
520            .collect::<Result<Vec<_>, _>>()?;
521
522        Ok(TransactionStatusMeta {
523            status: meta_status,
524            fee: meta.fee,
525            pre_balances: meta.pre_balances,
526            post_balances: meta.post_balances,
527            inner_instructions: Some(create_meta_inner_instructions(meta.inner_instructions)?),
528            log_messages: Some(meta.log_messages),
529            pre_token_balances: Some(create_token_balances(meta.pre_token_balances)?),
530            post_token_balances: Some(create_token_balances(meta.post_token_balances)?),
531            rewards: Some(meta_rewards),
532            loaded_addresses: create_loaded_addresses(
533                meta.loaded_writable_addresses,
534                meta.loaded_readonly_addresses,
535            )?,
536            return_data: if meta.return_data_none {
537                None
538            } else {
539                let data = meta.return_data.ok_or("failed to get return_data")?;
540                Some(TransactionReturnData {
541                    program_id: Pubkey::try_from(data.program_id.as_slice())
542                        .map_err(|_| "failed to parse program_id")?,
543                    data: data.data,
544                })
545            },
546            compute_units_consumed: meta.compute_units_consumed,
547            cost_units: meta.cost_units,
548        })
549    }
550
551    pub fn create_tx_error(
552        err: Option<&proto::TransactionError>,
553    ) -> CreateResult<Option<TransactionError>> {
554        err.map(|err| bincode::deserialize::<TransactionError>(&err.err))
555            .transpose()
556            .map_err(|_| "failed to decode TransactionError")
557    }
558
559    pub fn create_meta_inner_instructions(
560        ixs: Vec<proto::InnerInstructions>,
561    ) -> CreateResult<Vec<InnerInstructions>> {
562        ixs.into_iter().map(create_meta_inner_instruction).collect()
563    }
564
565    pub fn create_meta_inner_instruction(
566        ix: proto::InnerInstructions,
567    ) -> CreateResult<InnerInstructions> {
568        let mut instructions = vec![];
569        for ix in ix.instructions {
570            instructions.push(InnerInstruction {
571                instruction: CompiledInstruction {
572                    program_id_index: ix
573                        .program_id_index
574                        .try_into()
575                        .map_err(|_| "failed to decode CompiledInstruction.program_id_index)")?,
576                    accounts: ix.accounts,
577                    data: ix.data,
578                },
579                stack_height: ix.stack_height,
580            });
581        }
582        Ok(InnerInstructions {
583            index: ix
584                .index
585                .try_into()
586                .map_err(|_| "failed to decode InnerInstructions.index")?,
587            instructions,
588        })
589    }
590
591    pub fn create_rewards_obj(rewards: proto::Rewards) -> CreateResult<RewardsAndNumPartitions> {
592        Ok(RewardsAndNumPartitions {
593            rewards: rewards
594                .rewards
595                .into_iter()
596                .map(create_reward)
597                .collect::<Result<_, _>>()?,
598            num_partitions: rewards.num_partitions.map(|wrapper| wrapper.num_partitions),
599        })
600    }
601
602    pub fn create_reward(reward: proto::Reward) -> CreateResult<Reward> {
603        Ok(Reward {
604            pubkey: reward.pubkey,
605            lamports: reward.lamports,
606            post_balance: reward.post_balance,
607            reward_type: proto::RewardType::try_from(reward.reward_type)
608                .ok()
609                .and_then(|reward_type| match reward_type {
610                    proto::RewardType::Unspecified => None,
611                    proto::RewardType::Fee => Some(RewardType::Fee),
612                    proto::RewardType::Rent => Some(RewardType::Rent),
613                    proto::RewardType::Staking => Some(RewardType::Staking),
614                    proto::RewardType::Voting => Some(RewardType::Voting),
615                    proto::RewardType::DeactivatedStake => Some(RewardType::DeactivatedStake),
616                    // solana-transaction-status 4.1.x has no VATDebit variant.
617                    proto::RewardType::VatDebit => None,
618                }),
619            commission: if reward.commission.is_empty() {
620                None
621            } else {
622                Some(
623                    reward
624                        .commission
625                        .parse()
626                        .map_err(|_| "failed to parse reward commission")?,
627                )
628            },
629            commission_bps: if reward.commission_bps.is_empty() {
630                None
631            } else {
632                Some(
633                    reward
634                        .commission_bps
635                        .parse()
636                        .map_err(|_| "failed to parse reward commission_bps")?,
637                )
638            },
639        })
640    }
641
642    pub fn create_token_balances(
643        balances: Vec<proto::TokenBalance>,
644    ) -> CreateResult<Vec<TransactionTokenBalance>> {
645        let mut vec = Vec::with_capacity(balances.len());
646        for balance in balances {
647            let ui_amount = balance
648                .ui_token_amount
649                .ok_or("failed to get ui_token_amount")?;
650            vec.push(TransactionTokenBalance {
651                account_index: balance
652                    .account_index
653                    .try_into()
654                    .map_err(|_| "failed to parse account_index")?,
655                mint: balance.mint,
656                ui_token_amount: UiTokenAmount {
657                    ui_amount: Some(ui_amount.ui_amount),
658                    decimals: ui_amount
659                        .decimals
660                        .try_into()
661                        .map_err(|_| "failed to parse decimals")?,
662                    amount: ui_amount.amount,
663                    ui_amount_string: ui_amount.ui_amount_string,
664                },
665                owner: balance.owner,
666                program_id: balance.program_id,
667            });
668        }
669        Ok(vec)
670    }
671
672    pub fn create_loaded_addresses(
673        writable: Vec<Vec<u8>>,
674        readonly: Vec<Vec<u8>>,
675    ) -> CreateResult<LoadedAddresses> {
676        Ok(LoadedAddresses {
677            writable: create_pubkey_vec(writable)?,
678            readonly: create_pubkey_vec(readonly)?,
679        })
680    }
681
682    pub fn create_pubkey_vec(pubkeys: Vec<Vec<u8>>) -> CreateResult<Vec<Pubkey>> {
683        pubkeys
684            .iter()
685            .map(|pubkey| create_pubkey(pubkey.as_slice()))
686            .collect()
687    }
688
689    pub fn create_pubkey(pubkey: &[u8]) -> CreateResult<Pubkey> {
690        Pubkey::try_from(pubkey).map_err(|_| "failed to parse Pubkey")
691    }
692
693    pub fn create_account(
694        account: proto::SubscribeUpdateAccountInfo,
695    ) -> CreateResult<(Pubkey, Account)> {
696        let pubkey = create_pubkey(&account.pubkey)?;
697        let account = Account {
698            lamports: account.lamports,
699            data: account.data,
700            owner: create_pubkey(&account.owner)?,
701            executable: account.executable,
702            rent_epoch: account.rent_epoch,
703        };
704        Ok((pubkey, account))
705    }
706}
707
708#[cfg(all(test, feature = "convert"))]
709mod tests {
710    use {
711        super::{convert_from, convert_to, prelude as proto},
712        solana_transaction_status::RewardType,
713    };
714
715    fn proto_reward(reward_type: i32) -> proto::Reward {
716        proto::Reward {
717            pubkey: "11111111111111111111111111111111".to_owned(),
718            lamports: 1_000,
719            post_balance: 50_000,
720            reward_type,
721            commission: String::new(),
722            commission_bps: String::new(),
723        }
724    }
725
726    #[test]
727    fn deactivated_stake_round_trips() {
728        let encoded = convert_to::create_reward_type(Some(RewardType::DeactivatedStake)) as i32;
729        assert_eq!(encoded, proto::RewardType::DeactivatedStake as i32);
730
731        let decoded = convert_from::create_reward(proto_reward(encoded)).unwrap();
732        assert_eq!(decoded.reward_type, Some(RewardType::DeactivatedStake));
733    }
734
735    #[test]
736    fn vat_debit_decodes_as_none() {
737        let decoded =
738            convert_from::create_reward(proto_reward(proto::RewardType::VatDebit as i32)).unwrap();
739        assert_eq!(decoded.reward_type, None);
740    }
741
742    #[test]
743    fn unknown_reward_type_decodes_as_none() {
744        let decoded = convert_from::create_reward(proto_reward(99)).unwrap();
745        assert_eq!(decoded.reward_type, None);
746    }
747}