From f559fa8f60491122c7537a51d6f444474f8eadb7 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 3 Oct 2023 17:20:11 +0400 Subject: [PATCH] proto: add mod `convert_to`, `convert_from` (#190) --- CHANGELOG.md | 2 + Cargo.lock | 12 +- Cargo.toml | 8 +- examples/rust/Cargo.toml | 2 +- yellowstone-grpc-client/Cargo.toml | 4 +- yellowstone-grpc-geyser/Cargo.toml | 2 +- yellowstone-grpc-geyser/src/filters.rs | 18 +- yellowstone-grpc-geyser/src/grpc.rs | 46 +-- yellowstone-grpc-geyser/src/lib.rs | 1 - yellowstone-grpc-geyser/src/proto.rs | 241 ----------- yellowstone-grpc-proto/Cargo.toml | 6 +- yellowstone-grpc-proto/src/lib.rs | 548 +++++++++++++++++++++++++ 12 files changed, 602 insertions(+), 288 deletions(-) delete mode 100644 yellowstone-grpc-geyser/src/proto.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 568f876..9aa1ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The minor version will be incremented upon a breaking change and the patch versi ### Features +- proto: add mod `convert_to`, `convert_from` ([#190](https://github.com/rpcpool/yellowstone-grpc/pull/190)). + ### Fixes ### Breaking diff --git a/Cargo.lock b/Cargo.lock index 064ed8a..6d8cb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4466,7 +4466,7 @@ dependencies = [ [[package]] name = "yellowstone-grpc-client" -version = "1.10.0+solana.1.16.14" +version = "1.11.0+solana.1.16.14" dependencies = [ "bytes", "futures", @@ -4480,7 +4480,7 @@ dependencies = [ [[package]] name = "yellowstone-grpc-client-simple" -version = "1.9.0+solana.1.16.14" +version = "1.10.0+solana.1.16.14" dependencies = [ "anyhow", "backoff", @@ -4501,7 +4501,7 @@ dependencies = [ [[package]] name = "yellowstone-grpc-geyser" -version = "1.8.0+solana.1.16.14" +version = "1.9.0+solana.1.16.14" dependencies = [ "anyhow", "base64 0.21.2", @@ -4560,11 +4560,15 @@ dependencies = [ [[package]] name = "yellowstone-grpc-proto" -version = "1.9.0+solana.1.16.14" +version = "1.10.0+solana.1.16.14" dependencies = [ "anyhow", + "bincode", "prost", "protobuf-src", + "solana-account-decoder", + "solana-sdk", + "solana-transaction-status", "tonic", "tonic-build", ] diff --git a/Cargo.toml b/Cargo.toml index 7a602d0..a809444 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] members = [ - "examples/rust", # 1.9.0+solana.1.16.14 - "yellowstone-grpc-client", # 1.10.0+solana.1.16.14 - "yellowstone-grpc-geyser", # 1.8.0+solana.1.16.14 + "examples/rust", # 1.10.0+solana.1.16.14 + "yellowstone-grpc-client", # 1.11.0+solana.1.16.14 + "yellowstone-grpc-geyser", # 1.9.0+solana.1.16.14 "yellowstone-grpc-kafka", # 1.0.0+solana.1.16.14 - "yellowstone-grpc-proto", # 1.9.0+solana.1.16.14 + "yellowstone-grpc-proto", # 1.10.0+solana.1.16.14 ] [profile.release] diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 3e6ec7e..19252e4 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yellowstone-grpc-client-simple" -version = "1.9.0+solana.1.16.14" +version = "1.10.0+solana.1.16.14" authors = ["Triton One"] edition = "2021" publish = false diff --git a/yellowstone-grpc-client/Cargo.toml b/yellowstone-grpc-client/Cargo.toml index 53dc68e..b8d62d3 100644 --- a/yellowstone-grpc-client/Cargo.toml +++ b/yellowstone-grpc-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yellowstone-grpc-client" -version = "1.10.0+solana.1.16.14" +version = "1.11.0+solana.1.16.14" authors = ["Triton One"] edition = "2021" description = "Yellowstone gRPC Geyser Simple Client" @@ -16,7 +16,7 @@ http = "0.2.8" thiserror = "1.0" tonic = { version = "0.9.2", features = ["gzip", "tls", "tls-roots"] } tonic-health = "0.9.2" -yellowstone-grpc-proto = { path = "../yellowstone-grpc-proto", version = "1.9.0+solana.1.16.14" } +yellowstone-grpc-proto = { path = "../yellowstone-grpc-proto", version = "1.10.0+solana.1.16.14" } [dev-dependencies] tokio = { version = "1.21.2", features = ["macros"] } diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 7e31458..3466f29 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yellowstone-grpc-geyser" -version = "1.8.0+solana.1.16.14" +version = "1.9.0+solana.1.16.14" authors = ["Triton One"] edition = "2021" description = "Yellowstone gRPC Geyser Plugin" diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index e6dd979..3ffd1ce 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -9,15 +9,6 @@ use { Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, MessageRef, MessageSlot, MessageTransaction, }, - proto::{ - subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, - subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, - CommitmentLevel, SubscribeRequest, SubscribeRequestAccountsDataSlice, - SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, - SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, - SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, - SubscribeRequestFilterTransactions, SubscribeUpdate, - }, }, base64::{engine::general_purpose::STANDARD as base64_engine, Engine}, solana_sdk::{pubkey::Pubkey, signature::Signature}, @@ -27,6 +18,15 @@ use { iter::FromIterator, str::FromStr, }, + yellowstone_grpc_proto::prelude::{ + subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, + subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, + CommitmentLevel, SubscribeRequest, SubscribeRequestAccountsDataSlice, + SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, + SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, + SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, + SubscribeRequestFilterTransactions, SubscribeUpdate, + }, }; #[derive(Debug, Clone)] diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 97cf800..b646a37 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -3,18 +3,6 @@ use { config::{ConfigBlockFailAction, ConfigGrpc}, filters::{Filter, FilterAccountsDataSlice}, prom::{self, CONNECTIONS_TOTAL, MESSAGE_QUEUE_SIZE}, - proto::{ - self, - geyser_server::{Geyser, GeyserServer}, - subscribe_update::UpdateOneof, - CommitmentLevel, GetBlockHeightRequest, GetBlockHeightResponse, - GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, - GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest, - IsBlockhashValidResponse, PingRequest, PongResponse, SubscribeRequest, SubscribeUpdate, - SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlock, - SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, - SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, - }, version::VERSION, }, log::{error, info}, @@ -51,6 +39,20 @@ use { Request, Response, Result as TonicResult, Status, Streaming, }, tonic_health::server::health_reporter, + yellowstone_grpc_proto::{ + convert_to, + prelude::{ + geyser_server::{Geyser, GeyserServer}, + subscribe_update::UpdateOneof, + CommitmentLevel, GetBlockHeightRequest, GetBlockHeightResponse, + GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, + GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest, + IsBlockhashValidResponse, PingRequest, PongResponse, SubscribeRequest, SubscribeUpdate, + SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlock, + SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, + SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, + }, + }, }; #[derive(Debug, Clone)] @@ -155,8 +157,8 @@ impl MessageTransactionInfo { SubscribeUpdateTransactionInfo { signature: self.signature.as_ref().into(), is_vote: self.is_vote, - transaction: Some(proto::convert::create_transaction(&self.transaction)), - meta: Some(proto::convert::create_transaction_meta(&self.meta)), + transaction: Some(convert_to::create_transaction(&self.transaction)), + meta: Some(convert_to::create_transaction_meta(&self.meta)), index: self.index as u64, } } @@ -412,11 +414,9 @@ impl<'a> MessageRef<'a> { Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { slot: message.slot, blockhash: message.blockhash.clone(), - rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())), - block_time: message.block_time.map(proto::convert::create_timestamp), - block_height: message - .block_height - .map(proto::convert::create_block_height), + rewards: Some(convert_to::create_rewards(message.rewards.as_slice())), + block_time: message.block_time.map(convert_to::create_timestamp), + block_height: message.block_height.map(convert_to::create_block_height), parent_slot: message.parent_slot, parent_blockhash: message.parent_blockhash.clone(), executed_transaction_count: message.executed_transaction_count, @@ -441,11 +441,9 @@ impl<'a> MessageRef<'a> { Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { slot: message.slot, blockhash: message.blockhash.clone(), - rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())), - block_time: message.block_time.map(proto::convert::create_timestamp), - block_height: message - .block_height - .map(proto::convert::create_block_height), + rewards: Some(convert_to::create_rewards(message.rewards.as_slice())), + block_time: message.block_time.map(convert_to::create_timestamp), + block_height: message.block_height.map(convert_to::create_block_height), parent_slot: message.parent_slot, parent_blockhash: message.parent_blockhash.clone(), executed_transaction_count: message.executed_transaction_count, diff --git a/yellowstone-grpc-geyser/src/lib.rs b/yellowstone-grpc-geyser/src/lib.rs index 2ab9723..192cc6e 100644 --- a/yellowstone-grpc-geyser/src/lib.rs +++ b/yellowstone-grpc-geyser/src/lib.rs @@ -7,5 +7,4 @@ pub mod filters; pub mod grpc; pub mod plugin; pub mod prom; -pub mod proto; pub mod version; diff --git a/yellowstone-grpc-geyser/src/proto.rs b/yellowstone-grpc-geyser/src/proto.rs deleted file mode 100644 index 07b2f68..0000000 --- a/yellowstone-grpc-geyser/src/proto.rs +++ /dev/null @@ -1,241 +0,0 @@ -pub use yellowstone_grpc_proto::prelude::*; - -pub mod convert { - use { - solana_sdk::{ - clock::UnixTimestamp, - instruction::CompiledInstruction, - message::{ - v0::{LoadedMessage, MessageAddressTableLookup}, - LegacyMessage, MessageHeader, SanitizedMessage, - }, - pubkey::Pubkey, - signature::Signature, - transaction::SanitizedTransaction, - transaction_context::TransactionReturnData, - }, - solana_transaction_status::{ - InnerInstruction, InnerInstructions, Reward, RewardType, TransactionStatusMeta, - TransactionTokenBalance, - }, - }; - - pub fn create_transaction(tx: &SanitizedTransaction) -> super::Transaction { - super::Transaction { - signatures: tx - .signatures() - .iter() - .map(|signature| >::as_ref(signature).into()) - .collect(), - message: Some(create_message(tx.message())), - } - } - - pub fn create_message(message: &SanitizedMessage) -> super::Message { - match message { - SanitizedMessage::Legacy(LegacyMessage { message, .. }) => super::Message { - header: Some(create_header(&message.header)), - account_keys: message - .account_keys - .iter() - .map(|key| >::as_ref(key).into()) - .collect(), - recent_blockhash: message.recent_blockhash.to_bytes().into(), - instructions: message - .instructions - .iter() - .map(create_instruction) - .collect(), - versioned: false, - address_table_lookups: vec![], - }, - SanitizedMessage::V0(LoadedMessage { message, .. }) => super::Message { - header: Some(create_header(&message.header)), - account_keys: message - .account_keys - .iter() - .map(|key| >::as_ref(key).into()) - .collect(), - recent_blockhash: message.recent_blockhash.to_bytes().into(), - instructions: message - .instructions - .iter() - .map(create_instruction) - .collect(), - versioned: true, - address_table_lookups: message - .address_table_lookups - .iter() - .map(create_lookup) - .collect(), - }, - } - } - - pub const fn create_header(header: &MessageHeader) -> super::MessageHeader { - super::MessageHeader { - num_required_signatures: header.num_required_signatures as u32, - num_readonly_signed_accounts: header.num_readonly_signed_accounts as u32, - num_readonly_unsigned_accounts: header.num_readonly_unsigned_accounts as u32, - } - } - - pub fn create_instruction(ix: &CompiledInstruction) -> super::CompiledInstruction { - super::CompiledInstruction { - program_id_index: ix.program_id_index as u32, - accounts: ix.accounts.clone(), - data: ix.data.clone(), - } - } - - pub fn create_lookup(lookup: &MessageAddressTableLookup) -> super::MessageAddressTableLookup { - super::MessageAddressTableLookup { - account_key: >::as_ref(&lookup.account_key).into(), - writable_indexes: lookup.writable_indexes.clone(), - readonly_indexes: lookup.readonly_indexes.clone(), - } - } - - pub fn create_transaction_meta(meta: &TransactionStatusMeta) -> super::TransactionStatusMeta { - let TransactionStatusMeta { - status, - fee, - pre_balances, - post_balances, - inner_instructions, - log_messages, - pre_token_balances, - post_token_balances, - rewards, - loaded_addresses, - return_data, - compute_units_consumed, - } = meta; - let err = match status { - Ok(()) => None, - Err(err) => Some(super::TransactionError { - err: bincode::serialize(&err).expect("transaction error to serialize to bytes"), - }), - }; - let inner_instructions_none = inner_instructions.is_none(); - let inner_instructions = inner_instructions - .as_ref() - .map(|v| v.iter().map(create_inner_instructions).collect()) - .unwrap_or_default(); - let log_messages_none = log_messages.is_none(); - let log_messages = log_messages.clone().unwrap_or_default(); - let pre_token_balances = pre_token_balances - .as_ref() - .map(|v| v.iter().map(create_token_balance).collect()) - .unwrap_or_default(); - let post_token_balances = post_token_balances - .as_ref() - .map(|v| v.iter().map(create_token_balance).collect()) - .unwrap_or_default(); - let rewards = rewards - .as_ref() - .map(|vec| vec.iter().map(create_reward).collect()) - .unwrap_or_default(); - let loaded_writable_addresses = loaded_addresses - .writable - .iter() - .map(|key| >::as_ref(key).into()) - .collect(); - let loaded_readonly_addresses = loaded_addresses - .readonly - .iter() - .map(|key| >::as_ref(key).into()) - .collect(); - - super::TransactionStatusMeta { - err, - fee: *fee, - pre_balances: pre_balances.clone(), - post_balances: post_balances.clone(), - inner_instructions, - inner_instructions_none, - log_messages, - log_messages_none, - pre_token_balances, - post_token_balances, - rewards, - loaded_writable_addresses, - loaded_readonly_addresses, - return_data: return_data.as_ref().map(create_return_data), - return_data_none: return_data.is_none(), - compute_units_consumed: *compute_units_consumed, - } - } - - pub fn create_inner_instructions(instructions: &InnerInstructions) -> super::InnerInstructions { - super::InnerInstructions { - index: instructions.index as u32, - instructions: instructions - .instructions - .iter() - .map(create_inner_instruction) - .collect(), - } - } - - pub fn create_inner_instruction(instruction: &InnerInstruction) -> super::InnerInstruction { - super::InnerInstruction { - program_id_index: instruction.instruction.program_id_index as u32, - accounts: instruction.instruction.accounts.clone(), - data: instruction.instruction.data.clone(), - stack_height: instruction.stack_height, - } - } - - pub fn create_token_balance(balance: &TransactionTokenBalance) -> super::TokenBalance { - super::TokenBalance { - account_index: balance.account_index as u32, - mint: balance.mint.clone(), - ui_token_amount: Some(super::UiTokenAmount { - ui_amount: balance.ui_token_amount.ui_amount.unwrap_or_default(), - decimals: balance.ui_token_amount.decimals as u32, - amount: balance.ui_token_amount.amount.clone(), - ui_amount_string: balance.ui_token_amount.ui_amount_string.clone(), - }), - owner: balance.owner.clone(), - program_id: balance.program_id.clone(), - } - } - - pub fn create_reward(reward: &Reward) -> super::Reward { - super::Reward { - pubkey: reward.pubkey.clone(), - lamports: reward.lamports, - post_balance: reward.post_balance, - reward_type: match reward.reward_type { - None => super::RewardType::Unspecified, - Some(RewardType::Fee) => super::RewardType::Fee, - Some(RewardType::Rent) => super::RewardType::Rent, - Some(RewardType::Staking) => super::RewardType::Staking, - Some(RewardType::Voting) => super::RewardType::Voting, - } as i32, - commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(), - } - } - - pub fn create_return_data(return_data: &TransactionReturnData) -> super::ReturnData { - super::ReturnData { - program_id: return_data.program_id.to_bytes().into(), - data: return_data.data.clone(), - } - } - - pub fn create_rewards(rewards: &[Reward]) -> super::Rewards { - super::Rewards { - rewards: rewards.iter().map(create_reward).collect(), - } - } - - pub const fn create_block_height(block_height: u64) -> super::BlockHeight { - super::BlockHeight { block_height } - } - - pub const fn create_timestamp(timestamp: UnixTimestamp) -> super::UnixTimestamp { - super::UnixTimestamp { timestamp } - } -} diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index e98484b..df7383f 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yellowstone-grpc-proto" -version = "1.9.0+solana.1.16.14" +version = "1.10.0+solana.1.16.14" authors = ["Triton One"] edition = "2021" description = "Yellowstone gRPC Geyser Protobuf Definitions" @@ -10,7 +10,11 @@ license = "Apache-2.0" keywords = ["solana"] [dependencies] +bincode = "1.3.3" prost = "0.11.9" +solana-account-decoder = "=1.16.14" +solana-sdk = "=1.16.14" +solana-transaction-status = "=1.16.14" tonic = "0.9.2" [build-dependencies] diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 9a897cd..c4c61c4 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -19,3 +19,551 @@ pub mod prelude { pub use prost; pub use tonic; + +pub mod convert_to { + use { + super::prelude as proto, + solana_sdk::{ + clock::UnixTimestamp, + instruction::CompiledInstruction, + message::{ + v0::{LoadedMessage, MessageAddressTableLookup}, + LegacyMessage, MessageHeader, SanitizedMessage, + }, + pubkey::Pubkey, + signature::Signature, + transaction::SanitizedTransaction, + transaction_context::TransactionReturnData, + }, + solana_transaction_status::{ + InnerInstruction, InnerInstructions, Reward, RewardType, TransactionStatusMeta, + TransactionTokenBalance, + }, + }; + + pub fn create_transaction(tx: &SanitizedTransaction) -> proto::Transaction { + proto::Transaction { + signatures: tx + .signatures() + .iter() + .map(|signature| >::as_ref(signature).into()) + .collect(), + message: Some(create_message(tx.message())), + } + } + + pub fn create_message(message: &SanitizedMessage) -> proto::Message { + match message { + SanitizedMessage::Legacy(LegacyMessage { message, .. }) => proto::Message { + header: Some(create_header(&message.header)), + account_keys: message + .account_keys + .iter() + .map(|key| >::as_ref(key).into()) + .collect(), + recent_blockhash: message.recent_blockhash.to_bytes().into(), + instructions: message + .instructions + .iter() + .map(create_instruction) + .collect(), + versioned: false, + address_table_lookups: vec![], + }, + SanitizedMessage::V0(LoadedMessage { message, .. }) => proto::Message { + header: Some(create_header(&message.header)), + account_keys: message + .account_keys + .iter() + .map(|key| >::as_ref(key).into()) + .collect(), + recent_blockhash: message.recent_blockhash.to_bytes().into(), + instructions: message + .instructions + .iter() + .map(create_instruction) + .collect(), + versioned: true, + address_table_lookups: message + .address_table_lookups + .iter() + .map(create_lookup) + .collect(), + }, + } + } + + pub const fn create_header(header: &MessageHeader) -> proto::MessageHeader { + proto::MessageHeader { + num_required_signatures: header.num_required_signatures as u32, + num_readonly_signed_accounts: header.num_readonly_signed_accounts as u32, + num_readonly_unsigned_accounts: header.num_readonly_unsigned_accounts as u32, + } + } + + pub fn create_instruction(ix: &CompiledInstruction) -> proto::CompiledInstruction { + proto::CompiledInstruction { + program_id_index: ix.program_id_index as u32, + accounts: ix.accounts.clone(), + data: ix.data.clone(), + } + } + + pub fn create_lookup(lookup: &MessageAddressTableLookup) -> proto::MessageAddressTableLookup { + proto::MessageAddressTableLookup { + account_key: >::as_ref(&lookup.account_key).into(), + writable_indexes: lookup.writable_indexes.clone(), + readonly_indexes: lookup.readonly_indexes.clone(), + } + } + + pub fn create_transaction_meta(meta: &TransactionStatusMeta) -> proto::TransactionStatusMeta { + let TransactionStatusMeta { + status, + fee, + pre_balances, + post_balances, + inner_instructions, + log_messages, + pre_token_balances, + post_token_balances, + rewards, + loaded_addresses, + return_data, + compute_units_consumed, + } = meta; + let err = match status { + Ok(()) => None, + Err(err) => Some(proto::TransactionError { + err: bincode::serialize(&err).expect("transaction error to serialize to bytes"), + }), + }; + let inner_instructions_none = inner_instructions.is_none(); + let inner_instructions = inner_instructions + .as_ref() + .map(|v| v.iter().map(create_inner_instructions).collect()) + .unwrap_or_default(); + let log_messages_none = log_messages.is_none(); + let log_messages = log_messages.clone().unwrap_or_default(); + let pre_token_balances = pre_token_balances + .as_ref() + .map(|v| v.iter().map(create_token_balance).collect()) + .unwrap_or_default(); + let post_token_balances = post_token_balances + .as_ref() + .map(|v| v.iter().map(create_token_balance).collect()) + .unwrap_or_default(); + let rewards = rewards + .as_ref() + .map(|vec| vec.iter().map(create_reward).collect()) + .unwrap_or_default(); + let loaded_writable_addresses = loaded_addresses + .writable + .iter() + .map(|key| >::as_ref(key).into()) + .collect(); + let loaded_readonly_addresses = loaded_addresses + .readonly + .iter() + .map(|key| >::as_ref(key).into()) + .collect(); + + proto::TransactionStatusMeta { + err, + fee: *fee, + pre_balances: pre_balances.clone(), + post_balances: post_balances.clone(), + inner_instructions, + inner_instructions_none, + log_messages, + log_messages_none, + pre_token_balances, + post_token_balances, + rewards, + loaded_writable_addresses, + loaded_readonly_addresses, + return_data: return_data.as_ref().map(create_return_data), + return_data_none: return_data.is_none(), + compute_units_consumed: *compute_units_consumed, + } + } + + pub fn create_inner_instructions(instructions: &InnerInstructions) -> proto::InnerInstructions { + proto::InnerInstructions { + index: instructions.index as u32, + instructions: instructions + .instructions + .iter() + .map(create_inner_instruction) + .collect(), + } + } + + pub fn create_inner_instruction(instruction: &InnerInstruction) -> proto::InnerInstruction { + proto::InnerInstruction { + program_id_index: instruction.instruction.program_id_index as u32, + accounts: instruction.instruction.accounts.clone(), + data: instruction.instruction.data.clone(), + stack_height: instruction.stack_height, + } + } + + pub fn create_token_balance(balance: &TransactionTokenBalance) -> proto::TokenBalance { + proto::TokenBalance { + account_index: balance.account_index as u32, + mint: balance.mint.clone(), + ui_token_amount: Some(proto::UiTokenAmount { + ui_amount: balance.ui_token_amount.ui_amount.unwrap_or_default(), + decimals: balance.ui_token_amount.decimals as u32, + amount: balance.ui_token_amount.amount.clone(), + ui_amount_string: balance.ui_token_amount.ui_amount_string.clone(), + }), + owner: balance.owner.clone(), + program_id: balance.program_id.clone(), + } + } + + pub fn create_reward(reward: &Reward) -> proto::Reward { + proto::Reward { + pubkey: reward.pubkey.clone(), + lamports: reward.lamports, + post_balance: reward.post_balance, + reward_type: match reward.reward_type { + None => proto::RewardType::Unspecified, + Some(RewardType::Fee) => proto::RewardType::Fee, + Some(RewardType::Rent) => proto::RewardType::Rent, + Some(RewardType::Staking) => proto::RewardType::Staking, + Some(RewardType::Voting) => proto::RewardType::Voting, + } as i32, + commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(), + } + } + + pub fn create_return_data(return_data: &TransactionReturnData) -> proto::ReturnData { + proto::ReturnData { + program_id: return_data.program_id.to_bytes().into(), + data: return_data.data.clone(), + } + } + + pub fn create_rewards(rewards: &[Reward]) -> proto::Rewards { + proto::Rewards { + rewards: rewards.iter().map(create_reward).collect(), + } + } + + pub const fn create_block_height(block_height: u64) -> proto::BlockHeight { + proto::BlockHeight { block_height } + } + + pub const fn create_timestamp(timestamp: UnixTimestamp) -> proto::UnixTimestamp { + proto::UnixTimestamp { timestamp } + } +} + +pub mod convert_from { + use { + super::prelude as proto, + solana_account_decoder::parse_token::UiTokenAmount, + solana_sdk::{ + hash::{Hash, HASH_BYTES}, + instruction::CompiledInstruction, + message::{ + v0::{LoadedAddresses, Message as MessageV0, MessageAddressTableLookup}, + Message, MessageHeader, VersionedMessage, + }, + pubkey::Pubkey, + signature::Signature, + transaction::{TransactionError, VersionedTransaction}, + transaction_context::TransactionReturnData, + }, + solana_transaction_status::{ + ConfirmedBlock, InnerInstruction, InnerInstructions, Reward, RewardType, + TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, + VersionedTransactionWithStatusMeta, + }, + }; + + fn ensure_some(maybe_value: Option, message: impl Into) -> Result { + match maybe_value { + Some(value) => Ok(value), + None => Err(message.into()), + } + } + + pub fn create_block(block: proto::SubscribeUpdateBlock) -> Result { + let mut transactions = vec![]; + for tx in block.transactions { + transactions.push(create_tx_with_meta(tx)?); + } + + let mut rewards = vec![]; + for reward in ensure_some(block.rewards, "failed to get rewards")?.rewards { + rewards.push(create_reward(reward)?); + } + + Ok(ConfirmedBlock { + previous_blockhash: block.parent_blockhash, + blockhash: block.blockhash, + parent_slot: block.parent_slot, + transactions, + rewards, + block_time: Some(ensure_some( + block.block_time.map(|wrapper| wrapper.timestamp), + "failed to get block_time", + )?), + block_height: Some(ensure_some( + block.block_height.map(|wrapper| wrapper.block_height), + "failed to get block_height", + )?), + }) + } + + pub fn create_tx_with_meta( + tx: proto::SubscribeUpdateTransactionInfo, + ) -> Result { + let meta = ensure_some(tx.meta, "failed to get transaction meta")?; + let tx = ensure_some(tx.transaction, "failed to get transaction transaction")?; + + Ok(TransactionWithStatusMeta::Complete( + VersionedTransactionWithStatusMeta { + transaction: create_tx_versioned(tx)?, + meta: create_tx_meta(meta)?, + }, + )) + } + + pub fn create_tx_versioned(tx: proto::Transaction) -> Result { + let mut signatures = Vec::with_capacity(tx.signatures.len()); + for signature in tx.signatures { + signatures.push(match Signature::try_from(signature.as_slice()) { + Ok(signature) => signature, + Err(_error) => return Err("failed to parse Signature".to_owned()), + }); + } + + Ok(VersionedTransaction { + signatures, + message: create_message(ensure_some(tx.message, "failed to get message")?)?, + }) + } + + fn create_message(message: proto::Message) -> Result { + let header = ensure_some(message.header, "failed to get MessageHeader")?; + let header = MessageHeader { + num_required_signatures: ensure_some( + header.num_required_signatures.try_into().ok(), + "failed to parse num_required_signatures", + )?, + num_readonly_signed_accounts: ensure_some( + header.num_readonly_signed_accounts.try_into().ok(), + "failed to parse num_readonly_signed_accounts", + )?, + num_readonly_unsigned_accounts: ensure_some( + header.num_readonly_unsigned_accounts.try_into().ok(), + "failed to parse num_readonly_unsigned_accounts", + )?, + }; + + if message.recent_blockhash.len() != HASH_BYTES { + return Err("failed to parse hash".to_owned()); + } + + let mut instructions = Vec::with_capacity(message.instructions.len()); + for ix in message.instructions { + instructions.push(CompiledInstruction { + program_id_index: ensure_some( + ix.program_id_index.try_into().ok(), + "failed to decode CompiledInstruction.program_id_index)", + )?, + accounts: ix.accounts, + data: ix.data, + }); + } + + Ok(if message.versioned { + let mut address_table_lookups = Vec::with_capacity(message.address_table_lookups.len()); + for table in message.address_table_lookups { + address_table_lookups.push(MessageAddressTableLookup { + account_key: ensure_some( + Pubkey::try_from(table.account_key.as_slice()).ok(), + "failed to parse Pubkey", + )?, + writable_indexes: table.writable_indexes, + readonly_indexes: table.readonly_indexes, + }); + } + + VersionedMessage::V0(MessageV0 { + header, + account_keys: create_pubkey_vec(message.account_keys)?, + recent_blockhash: Hash::new(message.recent_blockhash.as_slice()), + instructions, + address_table_lookups, + }) + } else { + VersionedMessage::Legacy(Message { + header, + account_keys: create_pubkey_vec(message.account_keys)?, + recent_blockhash: Hash::new(message.recent_blockhash.as_slice()), + instructions, + }) + }) + } + + pub fn create_tx_meta( + meta: proto::TransactionStatusMeta, + ) -> Result { + let meta_status = match create_tx_error(meta.err.as_ref())? { + Some(err) => Err(err), + None => Ok(()), + }; + let mut meta_inner_instructions = vec![]; + for ix in meta.inner_instructions { + meta_inner_instructions.push(create_inner_instruction(ix)?); + } + let mut meta_rewards = vec![]; + for reward in meta.rewards { + meta_rewards.push(create_reward(reward)?); + } + + Ok(TransactionStatusMeta { + status: meta_status, + fee: meta.fee, + pre_balances: meta.pre_balances, + post_balances: meta.post_balances, + inner_instructions: Some(meta_inner_instructions), + log_messages: Some(meta.log_messages), + pre_token_balances: Some(create_token_balances(meta.pre_token_balances)?), + post_token_balances: Some(create_token_balances(meta.post_token_balances)?), + rewards: Some(meta_rewards), + loaded_addresses: create_loaded_addresses( + meta.loaded_writable_addresses, + meta.loaded_readonly_addresses, + )?, + return_data: if meta.return_data_none { + None + } else { + let data = ensure_some(meta.return_data, "failed to get return_data")?; + Some(TransactionReturnData { + program_id: ensure_some( + Pubkey::try_from(data.program_id.as_slice()).ok(), + "failed to parse program_id", + )?, + data: data.data, + }) + }, + compute_units_consumed: meta.compute_units_consumed, + }) + } + + pub fn create_tx_error( + err: Option<&proto::TransactionError>, + ) -> Result, String> { + ensure_some( + err.map(|err| bincode::deserialize::(&err.err)) + .transpose() + .ok(), + "failed to decode TransactionError", + ) + } + + fn create_inner_instruction(ix: proto::InnerInstructions) -> Result { + let mut instructions = vec![]; + for ix in ix.instructions { + instructions.push(InnerInstruction { + instruction: CompiledInstruction { + program_id_index: ensure_some( + ix.program_id_index.try_into().ok(), + "failed to decode CompiledInstruction.program_id_index)", + )?, + accounts: ix.accounts, + data: ix.data, + }, + stack_height: ix.stack_height, + }); + } + Ok(InnerInstructions { + index: ensure_some( + ix.index.try_into().ok(), + "failed to decode InnerInstructions.index", + )?, + instructions, + }) + } + + pub fn create_reward(reward: proto::Reward) -> Result { + Ok(Reward { + pubkey: reward.pubkey, + lamports: reward.lamports, + post_balance: reward.post_balance, + reward_type: match ensure_some( + proto::RewardType::from_i32(reward.reward_type), + "failed to parse reward_type", + )? { + proto::RewardType::Unspecified => None, + proto::RewardType::Fee => Some(RewardType::Fee), + proto::RewardType::Rent => Some(RewardType::Rent), + proto::RewardType::Staking => Some(RewardType::Staking), + proto::RewardType::Voting => Some(RewardType::Voting), + }, + commission: if reward.commission.is_empty() { + None + } else { + Some(ensure_some( + reward.commission.parse().ok(), + "failed to parse reward commission", + )?) + }, + }) + } + + fn create_token_balances( + balances: Vec, + ) -> Result, String> { + let mut vec = Vec::with_capacity(balances.len()); + for balance in balances { + let ui_amount = ensure_some(balance.ui_token_amount, "failed to get ui_token_amount")?; + vec.push(TransactionTokenBalance { + account_index: ensure_some( + balance.account_index.try_into().ok(), + "failed to parse account_index", + )?, + mint: balance.mint, + ui_token_amount: UiTokenAmount { + ui_amount: Some(ui_amount.ui_amount), + decimals: ensure_some( + ui_amount.decimals.try_into().ok(), + "failed to parse decimals", + )?, + amount: ui_amount.amount, + ui_amount_string: ui_amount.ui_amount_string, + }, + owner: balance.owner, + program_id: balance.program_id, + }); + } + Ok(vec) + } + + fn create_loaded_addresses( + writable: Vec>, + readonly: Vec>, + ) -> Result { + Ok(LoadedAddresses { + writable: create_pubkey_vec(writable)?, + readonly: create_pubkey_vec(readonly)?, + }) + } + + fn create_pubkey_vec(pubkeys: Vec>) -> Result, String> { + let mut vec = Vec::with_capacity(pubkeys.len()); + for pubkey in pubkeys { + vec.push(ensure_some( + Pubkey::try_from(pubkey.as_slice()).ok(), + "failed to parse Pubkey", + )?) + } + Ok(vec) + } +}