proto: add mod `convert_to`, `convert_from` (#190)

This commit is contained in:
Kirill Fomichev 2023-10-03 17:20:11 +04:00 committed by GitHub
parent a6cb542167
commit f559fa8f60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 602 additions and 288 deletions

View File

@ -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

12
Cargo.lock generated
View File

@ -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",
]

View File

@ -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]

View File

@ -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

View File

@ -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"] }

View File

@ -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"

View File

@ -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)]

View File

@ -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,

View File

@ -7,5 +7,4 @@ pub mod filters;
pub mod grpc;
pub mod plugin;
pub mod prom;
pub mod proto;
pub mod version;

View File

@ -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| <Signature as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::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: <Pubkey as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::as_ref(key).into())
.collect();
let loaded_readonly_addresses = loaded_addresses
.readonly
.iter()
.map(|key| <Pubkey as AsRef<[u8]>>::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 }
}
}

View File

@ -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]

View File

@ -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| <Signature as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::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: <Pubkey as AsRef<[u8]>>::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| <Pubkey as AsRef<[u8]>>::as_ref(key).into())
.collect();
let loaded_readonly_addresses = loaded_addresses
.readonly
.iter()
.map(|key| <Pubkey as AsRef<[u8]>>::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<T>(maybe_value: Option<T>, message: impl Into<String>) -> Result<T, String> {
match maybe_value {
Some(value) => Ok(value),
None => Err(message.into()),
}
}
pub fn create_block(block: proto::SubscribeUpdateBlock) -> Result<ConfirmedBlock, String> {
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<TransactionWithStatusMeta, String> {
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<VersionedTransaction, String> {
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<VersionedMessage, String> {
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<TransactionStatusMeta, String> {
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<Option<TransactionError>, String> {
ensure_some(
err.map(|err| bincode::deserialize::<TransactionError>(&err.err))
.transpose()
.ok(),
"failed to decode TransactionError",
)
}
fn create_inner_instruction(ix: proto::InnerInstructions) -> Result<InnerInstructions, String> {
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<Reward, String> {
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<proto::TokenBalance>,
) -> Result<Vec<TransactionTokenBalance>, 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<Vec<u8>>,
readonly: Vec<Vec<u8>>,
) -> Result<LoadedAddresses, String> {
Ok(LoadedAddresses {
writable: create_pubkey_vec(writable)?,
readonly: create_pubkey_vec(readonly)?,
})
}
fn create_pubkey_vec(pubkeys: Vec<Vec<u8>>) -> Result<Vec<Pubkey>, 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)
}
}