From e27be009fdcd8878f7970c56a7c3cccf17763c35 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 14:54:43 +0200 Subject: [PATCH 01/50] add messages crate --- Cargo.lock | 13 ++ Cargo.toml | 2 + yellowstone-grpc-geyser-messages/Cargo.toml | 22 +++ .../src/filter.rs | 93 +++++++++ .../src/geyser.rs | 155 ++++++++------- yellowstone-grpc-geyser-messages/src/lib.rs | 2 + yellowstone-grpc-geyser/Cargo.toml | 1 + yellowstone-grpc-geyser/src/filters.rs | 176 +++++------------- yellowstone-grpc-geyser/src/grpc.rs | 42 +++-- yellowstone-grpc-geyser/src/lib.rs | 1 - yellowstone-grpc-geyser/src/metrics.rs | 2 +- yellowstone-grpc-geyser/src/plugin.rs | 2 +- 12 files changed, 296 insertions(+), 215 deletions(-) create mode 100644 yellowstone-grpc-geyser-messages/Cargo.toml create mode 100644 yellowstone-grpc-geyser-messages/src/filter.rs rename yellowstone-grpc-geyser/src/message.rs => yellowstone-grpc-geyser-messages/src/geyser.rs (83%) create mode 100644 yellowstone-grpc-geyser-messages/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f468ff7a..131a54b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4514,6 +4514,19 @@ dependencies = [ "tonic", "tonic-health", "vergen", + "yellowstone-grpc-geyser-messages", + "yellowstone-grpc-proto", +] + +[[package]] +name = "yellowstone-grpc-geyser-messages" +version = "2.0.0" +dependencies = [ + "agave-geyser-plugin-interface", + "bincode", + "solana-sdk", + "solana-transaction-status", + "thiserror", "yellowstone-grpc-proto", ] diff --git a/Cargo.toml b/Cargo.toml index 81ecef70..1e4f6d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "examples/rust", # 2.0.0 "yellowstone-grpc-client", # 2.0.0 "yellowstone-grpc-geyser", # 2.0.0 + "yellowstone-grpc-geyser-messages", # 2.0.0 "yellowstone-grpc-proto", # 2.0.0 ] @@ -61,6 +62,7 @@ tonic-build = "0.12.1" tonic-health = "0.12.1" vergen = "9.0.0" yellowstone-grpc-client = { path = "yellowstone-grpc-client", version = "2.0.0" } +yellowstone-grpc-geyser-messages = { path = "yellowstone-grpc-geyser-messages", version = "2.0.0" } yellowstone-grpc-proto = { path = "yellowstone-grpc-proto", version = "2.0.0", default-features = false } [workspace.lints.clippy] diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml new file mode 100644 index 00000000..c203a371 --- /dev/null +++ b/yellowstone-grpc-geyser-messages/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "yellowstone-grpc-geyser-messages" +version = "2.0.0" +authors = { workspace = true } +edition = { workspace = true } +description = "Yellowstone gRPC Geyser Plugin Messages" +homepage = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +publish = { workspace = true } + +[dependencies] +agave-geyser-plugin-interface = { workspace = true } +bincode = { workspace = true } +solana-sdk = { workspace = true } +solana-transaction-status = { workspace = true } +thiserror = { workspace = true } +yellowstone-grpc-proto = { workspace = true, features = ["convert"] } + +[lints] +workspace = true diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-geyser-messages/src/filter.rs new file mode 100644 index 00000000..97033ddf --- /dev/null +++ b/yellowstone-grpc-geyser-messages/src/filter.rs @@ -0,0 +1,93 @@ +use std::{ + borrow::Borrow, + collections::HashSet, + sync::Arc, + time::{Duration, Instant}, +}; + +#[derive(Debug, thiserror::Error)] +pub enum FilterNameError { + #[error("oversized filter name (max allowed size {limit}), found {size}")] + Oversized { limit: usize, size: usize }, +} + +pub type FilterNameResult = Result; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FilterName(Arc); + +impl AsRef for FilterName { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Borrow for FilterName { + #[inline] + fn borrow(&self) -> &str { + &self.0[..] + } +} + +impl FilterName { + pub fn new(name: impl Into) -> Self { + Self(Arc::new(name.into())) + } + + pub fn is_uniq(&self) -> bool { + Arc::strong_count(&self.0) == 1 + } +} + +#[derive(Debug)] +pub struct FilterNames { + name_size_limit: usize, + names: HashSet, + names_size_limit: usize, + cleanup_ts: Instant, + cleanup_interval: Duration, +} + +impl FilterNames { + pub fn new( + name_size_limit: usize, + names_size_limit: usize, + cleanup_interval: Duration, + ) -> Self { + Self { + name_size_limit, + names: HashSet::with_capacity(names_size_limit), + names_size_limit, + cleanup_ts: Instant::now(), + cleanup_interval, + } + } + + pub fn try_clean(&mut self) { + if self.names.len() > self.names_size_limit + && self.cleanup_ts.elapsed() > self.cleanup_interval + { + self.names.retain(|name| !name.is_uniq()); + self.cleanup_ts = Instant::now(); + } + } + + pub fn get(&mut self, name: &str) -> FilterNameResult { + match self.names.get(name) { + Some(name) => Ok(name.clone()), + None => { + if name.len() > self.name_size_limit { + Err(FilterNameError::Oversized { + limit: self.name_size_limit, + size: name.len(), + }) + } else { + let name = FilterName::new(name); + self.names.insert(name.clone()); + Ok(name) + } + } + } + } +} diff --git a/yellowstone-grpc-geyser/src/message.rs b/yellowstone-grpc-geyser-messages/src/geyser.rs similarity index 83% rename from yellowstone-grpc-geyser/src/message.rs rename to yellowstone-grpc-geyser-messages/src/geyser.rs index 6438a2cb..1ce6f354 100644 --- a/yellowstone-grpc-geyser/src/message.rs +++ b/yellowstone-grpc-geyser-messages/src/geyser.rs @@ -9,9 +9,64 @@ use { }, solana_transaction_status::{Reward, TransactionStatusMeta}, std::sync::Arc, - yellowstone_grpc_proto::prelude::CommitmentLevel, + yellowstone_grpc_proto::prelude::CommitmentLevel as CommitmentLevelProto, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CommitmentLevel { + Processed, + Confirmed, + Finalized, +} + +impl PartialEq for CommitmentLevel { + fn eq(&self, other: &CommitmentLevelProto) -> bool { + matches!( + (self, other), + (Self::Processed, CommitmentLevelProto::Processed) + | (Self::Confirmed, CommitmentLevelProto::Confirmed) + | (Self::Finalized, CommitmentLevelProto::Finalized) + ) + } +} + +impl From for CommitmentLevel { + fn from(status: CommitmentLevelProto) -> Self { + match status { + CommitmentLevelProto::Processed => Self::Processed, + CommitmentLevelProto::Confirmed => Self::Confirmed, + CommitmentLevelProto::Finalized => Self::Finalized, + } + } +} + +impl From for CommitmentLevel { + fn from(status: SlotStatus) -> Self { + match status { + SlotStatus::Processed => Self::Processed, + SlotStatus::Confirmed => Self::Confirmed, + SlotStatus::Rooted => Self::Finalized, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct MessageSlot { + pub slot: u64, + pub parent: Option, + pub status: CommitmentLevel, +} + +impl From<(u64, Option, SlotStatus)> for MessageSlot { + fn from((slot, parent, status): (u64, Option, SlotStatus)) -> Self { + Self { + slot, + parent, + status: status.into(), + } + } +} + #[derive(Debug, Clone)] pub struct MessageAccountInfo { pub pubkey: Pubkey, @@ -50,27 +105,6 @@ impl<'a> From<(&'a ReplicaAccountInfoV3<'a>, u64, bool)> for MessageAccount { } } -#[derive(Debug, Clone, Copy)] -pub struct MessageSlot { - pub slot: u64, - pub parent: Option, - pub status: CommitmentLevel, -} - -impl From<(u64, Option, SlotStatus)> for MessageSlot { - fn from((slot, parent, status): (u64, Option, SlotStatus)) -> Self { - Self { - slot, - parent, - status: match status { - SlotStatus::Processed => CommitmentLevel::Processed, - SlotStatus::Confirmed => CommitmentLevel::Confirmed, - SlotStatus::Rooted => CommitmentLevel::Finalized, - }, - } - } -} - #[derive(Debug, Clone)] pub struct MessageTransactionInfo { pub signature: Signature, @@ -127,6 +161,37 @@ impl From<&ReplicaEntryInfoV2<'_>> for MessageEntry { } } +#[derive(Debug, Clone)] +pub struct MessageBlockMeta { + pub parent_slot: u64, + pub slot: u64, + pub parent_blockhash: String, + pub blockhash: String, + pub rewards: Vec, + pub num_partitions: Option, + pub block_time: Option, + pub block_height: Option, + pub executed_transaction_count: u64, + pub entries_count: u64, +} + +impl<'a> From<&'a ReplicaBlockInfoV4<'a>> for MessageBlockMeta { + fn from(blockinfo: &'a ReplicaBlockInfoV4<'a>) -> Self { + Self { + parent_slot: blockinfo.parent_slot, + slot: blockinfo.slot, + parent_blockhash: blockinfo.parent_blockhash.to_string(), + blockhash: blockinfo.blockhash.to_string(), + rewards: blockinfo.rewards.rewards.clone(), + num_partitions: blockinfo.rewards.num_partitions, + block_time: blockinfo.block_time, + block_height: blockinfo.block_height, + executed_transaction_count: blockinfo.executed_transaction_count, + entries_count: blockinfo.entry_count, + } + } +} + #[derive(Debug, Clone)] pub struct MessageBlock { pub meta: Arc, @@ -162,45 +227,14 @@ impl } } -#[derive(Debug, Clone)] -pub struct MessageBlockMeta { - pub parent_slot: u64, - pub slot: u64, - pub parent_blockhash: String, - pub blockhash: String, - pub rewards: Vec, - pub num_partitions: Option, - pub block_time: Option, - pub block_height: Option, - pub executed_transaction_count: u64, - pub entries_count: u64, -} - -impl<'a> From<&'a ReplicaBlockInfoV4<'a>> for MessageBlockMeta { - fn from(blockinfo: &'a ReplicaBlockInfoV4<'a>) -> Self { - Self { - parent_slot: blockinfo.parent_slot, - slot: blockinfo.slot, - parent_blockhash: blockinfo.parent_blockhash.to_string(), - blockhash: blockinfo.blockhash.to_string(), - rewards: blockinfo.rewards.rewards.clone(), - num_partitions: blockinfo.rewards.num_partitions, - block_time: blockinfo.block_time, - block_height: blockinfo.block_height, - executed_transaction_count: blockinfo.executed_transaction_count, - entries_count: blockinfo.entry_count, - } - } -} - #[derive(Debug, Clone)] pub enum Message { Slot(MessageSlot), Account(MessageAccount), Transaction(MessageTransaction), Entry(Arc), - Block(Arc), BlockMeta(Arc), + Block(Arc), } impl Message { @@ -210,19 +244,8 @@ impl Message { Self::Account(msg) => msg.slot, Self::Transaction(msg) => msg.slot, Self::Entry(msg) => msg.slot, - Self::Block(msg) => msg.meta.slot, Self::BlockMeta(msg) => msg.slot, - } - } - - pub const fn kind(&self) -> &'static str { - match self { - Self::Slot(_) => "Slot", - Self::Account(_) => "Account", - Self::Transaction(_) => "Transaction", - Self::Entry(_) => "Entry", - Self::Block(_) => "Block", - Self::BlockMeta(_) => "BlockMeta", + Self::Block(msg) => msg.meta.slot, } } } diff --git a/yellowstone-grpc-geyser-messages/src/lib.rs b/yellowstone-grpc-geyser-messages/src/lib.rs new file mode 100644 index 00000000..1e231136 --- /dev/null +++ b/yellowstone-grpc-geyser-messages/src/lib.rs @@ -0,0 +1,2 @@ +pub mod filter; +pub mod geyser; diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 84636d05..e084f718 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -45,6 +45,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs"] } tokio-stream = { workspace = true } tonic = { workspace = true, features = ["gzip", "zstd", "tls", "tls-roots"] } tonic-health = { workspace = true } +yellowstone-grpc-geyser-messages = { workspace = true } yellowstone-grpc-proto = { workspace = true, features = ["convert"] } [build-dependencies] diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 29fc14c8..dc223a91 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -1,24 +1,25 @@ use { - crate::{ - config::{ - ConfigGrpcFilters, ConfigGrpcFiltersAccounts, ConfigGrpcFiltersBlocks, - ConfigGrpcFiltersBlocksMeta, ConfigGrpcFiltersEntry, ConfigGrpcFiltersSlots, - ConfigGrpcFiltersTransactions, - }, - message::{ - Message, MessageAccount, MessageAccountInfo, MessageBlock, MessageBlockMeta, - MessageEntry, MessageSlot, MessageTransaction, MessageTransactionInfo, - }, + crate::config::{ + ConfigGrpcFilters, ConfigGrpcFiltersAccounts, ConfigGrpcFiltersBlocks, + ConfigGrpcFiltersBlocksMeta, ConfigGrpcFiltersEntry, ConfigGrpcFiltersSlots, + ConfigGrpcFiltersTransactions, }, base64::{engine::general_purpose::STANDARD as base64_engine, Engine}, solana_sdk::{pubkey::Pubkey, signature::Signature}, spl_token_2022::{generic_token_account::GenericTokenAccount, state::Account as TokenAccount}, std::{ - borrow::Borrow, collections::{HashMap, HashSet}, + ops::Range, str::FromStr, sync::Arc, - time::{Duration, Instant}, + }, + yellowstone_grpc_geyser_messages::{ + filter::{FilterName, FilterNames}, + geyser::{ + CommitmentLevel, Message, MessageAccount, MessageAccountInfo, MessageBlock, + MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, + MessageTransactionInfo, + }, }, yellowstone_grpc_proto::{ convert_to, @@ -26,8 +27,8 @@ use { subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, - subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest, - SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, + subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, + SubscribeRequest, SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, @@ -40,93 +41,6 @@ use { }, }; -#[derive(Debug, thiserror::Error)] -pub enum FilterError { - #[error("invalid filter name (max allowed size {limit}), found {size}")] - OversizedFilterName { limit: usize, size: usize }, -} - -pub type FilterResult = Result; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FilterName(Arc); - -impl AsRef for FilterName { - #[inline] - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Borrow for FilterName { - #[inline] - fn borrow(&self) -> &str { - &self.0[..] - } -} - -impl FilterName { - pub fn new(name: impl Into) -> Self { - Self(Arc::new(name.into())) - } - - pub fn is_uniq(&self) -> bool { - Arc::strong_count(&self.0) == 1 - } -} - -#[derive(Debug)] -pub struct FilterNames { - name_size_limit: usize, - names: HashSet, - names_size_limit: usize, - cleanup_ts: Instant, - cleanup_interval: Duration, -} - -impl FilterNames { - pub fn new( - name_size_limit: usize, - names_size_limit: usize, - cleanup_interval: Duration, - ) -> Self { - Self { - name_size_limit, - names: HashSet::with_capacity(names_size_limit), - names_size_limit, - cleanup_ts: Instant::now(), - cleanup_interval, - } - } - - pub fn try_clean(&mut self) { - if self.names.len() > self.names_size_limit - && self.cleanup_ts.elapsed() > self.cleanup_interval - { - self.names.retain(|name| !name.is_uniq()); - self.cleanup_ts = Instant::now(); - } - } - - pub fn get(&mut self, name: &str) -> FilterResult { - match self.names.get(name) { - Some(name) => Ok(name.clone()), - None => { - if name.len() > self.name_size_limit { - Err(FilterError::OversizedFilterName { - limit: self.name_size_limit, - size: name.len(), - }) - } else { - let name = FilterName::new(name); - self.names.insert(name.clone()); - Ok(name) - } - } - } - } -} - #[derive(Debug, Clone)] pub enum FilteredMessage<'a> { Slot(&'a MessageSlot), @@ -141,15 +55,16 @@ pub enum FilteredMessage<'a> { impl<'a> FilteredMessage<'a> { fn as_proto_account( message: &MessageAccountInfo, - accounts_data_slice: &[FilterAccountsDataSlice], + accounts_data_slice: &[Range], ) -> SubscribeUpdateAccountInfo { let data = if accounts_data_slice.is_empty() { message.data.clone() } else { - let mut data = Vec::with_capacity(accounts_data_slice.iter().map(|ds| ds.length).sum()); - for data_slice in accounts_data_slice { - if message.data.len() >= data_slice.end { - data.extend_from_slice(&message.data[data_slice.start..data_slice.end]); + let mut data = + Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); + for slice in accounts_data_slice { + if message.data.len() >= slice.end { + data.extend_from_slice(&message.data[slice.start..slice.end]); } } data @@ -187,7 +102,7 @@ impl<'a> FilteredMessage<'a> { } } - pub fn as_proto(&self, accounts_data_slice: &[FilterAccountsDataSlice]) -> UpdateOneof { + pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { match self { Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { slot: message.slot, @@ -283,7 +198,7 @@ pub struct Filter { blocks: FilterBlocks, blocks_meta: FilterBlocksMeta, commitment: CommitmentLevel, - accounts_data_slice: Vec, + accounts_data_slice: Vec>, ping: Option, } @@ -342,9 +257,11 @@ impl Filter { fn decode_commitment(commitment: Option) -> anyhow::Result { let commitment = commitment.unwrap_or(CommitmentLevel::Processed as i32); - CommitmentLevel::try_from(commitment).map_err(|_error| { - anyhow::anyhow!("failed to create CommitmentLevel from {commitment:?}") - }) + CommitmentLevelProto::try_from(commitment) + .map(Into::into) + .map_err(|_error| { + anyhow::anyhow!("failed to create CommitmentLevel from {commitment:?}") + }) } fn decode_pubkeys<'a>( @@ -1190,25 +1107,19 @@ impl FilterBlocksMeta { } #[derive(Debug, Clone, Copy)] -pub struct FilterAccountsDataSlice { - pub start: usize, - pub end: usize, - pub length: usize, -} - -impl From<&SubscribeRequestAccountsDataSlice> for FilterAccountsDataSlice { - fn from(data_slice: &SubscribeRequestAccountsDataSlice) -> Self { - Self { - start: data_slice.offset as usize, - end: (data_slice.offset + data_slice.length) as usize, - length: data_slice.length as usize, - } - } -} +pub struct FilterAccountsDataSlice; impl FilterAccountsDataSlice { - pub fn create(slices: &[SubscribeRequestAccountsDataSlice]) -> anyhow::Result> { - let slices = slices.iter().map(Into::into).collect::>(); + pub fn create( + slices: &[SubscribeRequestAccountsDataSlice], + ) -> anyhow::Result>> { + let slices = slices + .iter() + .map(|s| Range { + start: s.offset as usize, + end: (s.offset + s.length) as usize, + }) + .collect::>(); for (i, slice_a) in slices.iter().enumerate() { // check order @@ -1230,11 +1141,7 @@ impl FilterAccountsDataSlice { mod tests { use { super::{FilterName, FilterNames, FilteredMessage}, - crate::{ - config::ConfigGrpcFilters, - filters::Filter, - message::{Message, MessageTransaction, MessageTransactionInfo}, - }, + crate::{config::ConfigGrpcFilters, filters::Filter}, solana_sdk::{ hash::Hash, message::{v0::LoadedAddresses, Message as SolMessage, MessageHeader}, @@ -1244,6 +1151,9 @@ mod tests { }, solana_transaction_status::TransactionStatusMeta, std::{collections::HashMap, sync::Arc, time::Duration}, + yellowstone_grpc_geyser_messages::geyser::{ + Message, MessageTransaction, MessageTransactionInfo, + }, yellowstone_grpc_proto::geyser::{ SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterTransactions, }, diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 66de0d35..15a51d6f 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -1,8 +1,7 @@ use { crate::{ config::{ConfigBlockFailAction, ConfigGrpc, ConfigGrpcFilters}, - filters::{Filter, FilterNames}, - message::{Message, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransactionInfo}, + filters::Filter, metrics::{self, DebugClientMessage}, version::GrpcVersionInfo, }, @@ -36,13 +35,20 @@ use { Request, Response, Result as TonicResult, Status, Streaming, }, tonic_health::server::health_reporter, + yellowstone_grpc_geyser_messages::{ + filter::FilterNames, + geyser::{ + CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, + MessageTransactionInfo, + }, + }, yellowstone_grpc_proto::prelude::{ geyser_server::{Geyser, GeyserServer}, subscribe_update::UpdateOneof, - CommitmentLevel, GetBlockHeightRequest, GetBlockHeightResponse, GetLatestBlockhashRequest, - GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, GetVersionRequest, - GetVersionResponse, IsBlockhashValidRequest, IsBlockhashValidResponse, PingRequest, - PongResponse, SubscribeRequest, SubscribeUpdate, SubscribeUpdatePing, + CommitmentLevel as CommitmentLevelProto, GetBlockHeightRequest, GetBlockHeightResponse, + GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, + GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest, IsBlockhashValidResponse, + PingRequest, PongResponse, SubscribeRequest, SubscribeUpdate, SubscribeUpdatePing, }, }; @@ -153,10 +159,12 @@ impl BlockMetaStorage { fn parse_commitment(commitment: Option) -> Result { let commitment = commitment.unwrap_or(CommitmentLevel::Processed as i32); - CommitmentLevel::try_from(commitment).map_err(|_error| { - let msg = format!("failed to create CommitmentLevel from {commitment:?}"); - Status::unknown(msg) - }) + CommitmentLevelProto::try_from(commitment) + .map(Into::into) + .map_err(|_error| { + let msg = format!("failed to create CommitmentLevel from {commitment:?}"); + Status::unknown(msg) + }) } async fn get_block( @@ -529,13 +537,21 @@ impl GrpcService { // If we already build Block message, new message will be a problem if slot_messages.sealed && !(matches!(&message, Message::Entry(_)) && slot_messages.entries_count == 0) { - metrics::update_invalid_blocks(format!("unexpected message {}", message.kind())); + let kind = match &message { + Message::Slot(_) => "Slot", + Message::Account(_) => "Account", + Message::Transaction(_) => "Transaction", + Message::Entry(_) => "Entry", + Message::BlockMeta(_) => "BlockMeta", + Message::Block(_) => "Block", + }; + metrics::update_invalid_blocks(format!("unexpected message {}", kind)); match block_fail_action { ConfigBlockFailAction::Log => { - error!("unexpected message #{} -- {} (invalid order)", message.get_slot(), message.kind()); + error!("unexpected message #{} -- {} (invalid order)", message.get_slot(), kind); } ConfigBlockFailAction::Panic => { - panic!("unexpected message #{} -- {} (invalid order)", message.get_slot(), message.kind()); + panic!("unexpected message #{} -- {} (invalid order)", message.get_slot(), kind); } } } diff --git a/yellowstone-grpc-geyser/src/lib.rs b/yellowstone-grpc-geyser/src/lib.rs index c043be3c..f5f4d3f2 100644 --- a/yellowstone-grpc-geyser/src/lib.rs +++ b/yellowstone-grpc-geyser/src/lib.rs @@ -1,7 +1,6 @@ pub mod config; pub mod filters; pub mod grpc; -pub mod message; pub mod metrics; pub mod plugin; pub mod version; diff --git a/yellowstone-grpc-geyser/src/metrics.rs b/yellowstone-grpc-geyser/src/metrics.rs index 447002a7..3fc74b91 100644 --- a/yellowstone-grpc-geyser/src/metrics.rs +++ b/yellowstone-grpc-geyser/src/metrics.rs @@ -24,7 +24,7 @@ use { sync::{mpsc, oneshot, Notify}, task::JoinHandle, }, - yellowstone_grpc_proto::prelude::CommitmentLevel, + yellowstone_grpc_geyser_messages::geyser::CommitmentLevel, }; lazy_static::lazy_static! { diff --git a/yellowstone-grpc-geyser/src/plugin.rs b/yellowstone-grpc-geyser/src/plugin.rs index 59f76ea2..8bc1e987 100644 --- a/yellowstone-grpc-geyser/src/plugin.rs +++ b/yellowstone-grpc-geyser/src/plugin.rs @@ -2,7 +2,6 @@ use { crate::{ config::Config, grpc::GrpcService, - message::Message, metrics::{self, PrometheusService}, }, agave_geyser_plugin_interface::geyser_plugin_interface::{ @@ -22,6 +21,7 @@ use { runtime::{Builder, Runtime}, sync::{mpsc, Notify}, }, + yellowstone_grpc_geyser_messages::geyser::Message, }; #[derive(Debug)] From 838c14bd7827dad00670697f20c6746e3de9cd51 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 15:19:31 +0200 Subject: [PATCH 02/50] custom weak build --- Cargo.lock | 2 +- yellowstone-grpc-geyser-messages/Cargo.toml | 1 - .../src/geyser.rs | 22 ------ yellowstone-grpc-geyser/Cargo.toml | 2 +- yellowstone-grpc-proto/Cargo.toml | 2 + yellowstone-grpc-proto/build.rs | 79 +++++++++++++++++++ yellowstone-grpc-proto/src/lib.rs | 34 ++++++++ 7 files changed, 117 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 131a54b7..45c8845f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4527,7 +4527,6 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "thiserror", - "yellowstone-grpc-proto", ] [[package]] @@ -4543,6 +4542,7 @@ dependencies = [ "solana-transaction-status", "tonic", "tonic-build", + "yellowstone-grpc-geyser-messages", ] [[package]] diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml index c203a371..5fd72276 100644 --- a/yellowstone-grpc-geyser-messages/Cargo.toml +++ b/yellowstone-grpc-geyser-messages/Cargo.toml @@ -16,7 +16,6 @@ bincode = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } thiserror = { workspace = true } -yellowstone-grpc-proto = { workspace = true, features = ["convert"] } [lints] workspace = true diff --git a/yellowstone-grpc-geyser-messages/src/geyser.rs b/yellowstone-grpc-geyser-messages/src/geyser.rs index 1ce6f354..1dca4bec 100644 --- a/yellowstone-grpc-geyser-messages/src/geyser.rs +++ b/yellowstone-grpc-geyser-messages/src/geyser.rs @@ -9,7 +9,6 @@ use { }, solana_transaction_status::{Reward, TransactionStatusMeta}, std::sync::Arc, - yellowstone_grpc_proto::prelude::CommitmentLevel as CommitmentLevelProto, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -19,27 +18,6 @@ pub enum CommitmentLevel { Finalized, } -impl PartialEq for CommitmentLevel { - fn eq(&self, other: &CommitmentLevelProto) -> bool { - matches!( - (self, other), - (Self::Processed, CommitmentLevelProto::Processed) - | (Self::Confirmed, CommitmentLevelProto::Confirmed) - | (Self::Finalized, CommitmentLevelProto::Finalized) - ) - } -} - -impl From for CommitmentLevel { - fn from(status: CommitmentLevelProto) -> Self { - match status { - CommitmentLevelProto::Processed => Self::Processed, - CommitmentLevelProto::Confirmed => Self::Confirmed, - CommitmentLevelProto::Finalized => Self::Finalized, - } - } -} - impl From for CommitmentLevel { fn from(status: SlotStatus) -> Self { match status { diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index e084f718..627303b1 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -46,7 +46,7 @@ tokio-stream = { workspace = true } tonic = { workspace = true, features = ["gzip", "zstd", "tls", "tls-roots"] } tonic-health = { workspace = true } yellowstone-grpc-geyser-messages = { workspace = true } -yellowstone-grpc-proto = { workspace = true, features = ["convert"] } +yellowstone-grpc-proto = { workspace = true, features = ["convert", "geyser_weak"] } [build-dependencies] anyhow = { workspace = true } diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index 59fe5e92..8a904694 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -17,6 +17,7 @@ solana-account-decoder = { workspace = true, optional = true } solana-sdk = { workspace = true, optional = true } solana-transaction-status = { workspace = true, optional = true } tonic = { workspace = true } +yellowstone-grpc-geyser-messages = { workspace = true, optional = true } [build-dependencies] anyhow = { workspace = true } @@ -25,6 +26,7 @@ tonic-build = { workspace = true } [features] convert = ["dep:solana-account-decoder", "dep:solana-sdk", "dep:solana-transaction-status"] +geyser_weak = ["dep:yellowstone-grpc-geyser-messages"] tonic-compression = ["tonic/gzip", "tonic/zstd"] default = ["convert", "tonic-compression"] diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index 66db00ba..260932dd 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -1,5 +1,84 @@ +use tonic_build::manual::{Builder, Method, Service}; + fn main() -> anyhow::Result<()> { std::env::set_var("PROTOC", protobuf_src::protoc()); + + // build protos tonic_build::compile_protos("proto/geyser.proto")?; + + // build with accepting our custom struct + let geyser_service = Service::builder() + .name("Geyser") + .package("geyser") + .method( + Method::builder() + .name("subscribe") + .route_name("Subscribe") + .input_type("crate::geyser::SubscribeRequest") + .output_type("crate::geyser::SubscribeUpdate") + // .output_type("crate::weak::FilteredMessageWithNames") + .codec_path("tonic::codec::ProstCodec") + // .codec_path("crate::geyser_custom::SubscribeCodec") + .client_streaming() + .server_streaming() + .build(), + ) + .method( + Method::builder() + .name("ping") + .route_name("Ping") + .input_type("crate::geyser::PingRequest") + .output_type("crate::geyser::PongResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .method( + Method::builder() + .name("get_latest_blockhash") + .route_name("GetLatestBlockhash") + .input_type("crate::geyser::GetLatestBlockhashRequest") + .output_type("crate::geyser::GetLatestBlockhashResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .method( + Method::builder() + .name("get_block_height") + .route_name("GetBlockHeight") + .input_type("crate::geyser::GetBlockHeightRequest") + .output_type("crate::geyser::GetBlockHeightResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .method( + Method::builder() + .name("get_slot") + .route_name("GetSlot") + .input_type("crate::geyser::GetSlotRequest") + .output_type("crate::geyser::GetSlotResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .method( + Method::builder() + .name("is_blockhash_valid") + .route_name("IsBlockhashValid") + .input_type("crate::geyser::IsBlockhashValidRequest") + .output_type("crate::geyser::IsBlockhashValidResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .method( + Method::builder() + .name("get_version") + .route_name("GetVersion") + .input_type("crate::geyser::GetVersionRequest") + .output_type("crate::geyser::GetVersionResponse") + .codec_path("tonic::codec::ProstCodec") + .build(), + ) + .build(); + Builder::new().compile(&[geyser_service]); + Ok(()) } diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 40868a21..7ef338f5 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -5,6 +5,40 @@ pub mod geyser { #![allow(clippy::missing_const_for_fn)] tonic::include_proto!("geyser"); + + #[cfg(feature = "geyser_weak")] + use yellowstone_grpc_geyser_messages::geyser::CommitmentLevel as CommitmentLevelMessages; + + #[cfg(feature = "geyser_weak")] + impl PartialEq for CommitmentLevelMessages { + fn eq(&self, other: &CommitmentLevel) -> bool { + matches!( + (self, other), + (Self::Processed, CommitmentLevel::Processed) + | (Self::Confirmed, CommitmentLevel::Confirmed) + | (Self::Finalized, CommitmentLevel::Finalized) + ) + } + } + + #[cfg(feature = "geyser_weak")] + impl From for CommitmentLevelMessages { + fn from(status: CommitmentLevel) -> Self { + match status { + CommitmentLevel::Processed => Self::Processed, + CommitmentLevel::Confirmed => Self::Confirmed, + CommitmentLevel::Finalized => Self::Finalized, + } + } + } +} + +#[cfg(feature = "geyser_weak")] +pub mod geyser_weak { + #![allow(clippy::clone_on_ref_ptr)] + #![allow(clippy::missing_const_for_fn)] + + tonic::include_proto!("geyser.Geyser"); } pub mod solana { From 95977cc78bdc9e2282fe04cb22bc6f1969ac7d58 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 17:09:11 +0200 Subject: [PATCH 03/50] custom grpc server --- .github/workflows/test.yml | 2 + Cargo.lock | 1 + yellowstone-grpc-geyser-messages/Cargo.toml | 1 + .../src/filter.rs | 24 ++++-- yellowstone-grpc-geyser/src/filters.rs | 42 +++++++---- yellowstone-grpc-geyser/src/grpc.rs | 33 ++++---- yellowstone-grpc-proto/build.rs | 22 ++++-- yellowstone-grpc-proto/src/lib.rs | 75 +++++++++++++++++++ 8 files changed, 156 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09f11a1a..9ae6dbe3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,6 +96,8 @@ jobs: run: cargo check -p yellowstone-grpc-geyser --all-targets --tests - name: check features in `proto` run: cargo check -p yellowstone-grpc-proto --all-targets --tests + - name: check features in `proto` + run: cargo check -p yellowstone-grpc-proto --all-targets --tests --all-features - name: Build run: ./ci/cargo-build-test.sh diff --git a/Cargo.lock b/Cargo.lock index 45c8845f..f4d76466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4527,6 +4527,7 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "thiserror", + "tonic", ] [[package]] diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml index 5fd72276..a36cc54e 100644 --- a/yellowstone-grpc-geyser-messages/Cargo.toml +++ b/yellowstone-grpc-geyser-messages/Cargo.toml @@ -16,6 +16,7 @@ bincode = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } thiserror = { workspace = true } +tonic = { workspace = true } [lints] workspace = true diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-geyser-messages/src/filter.rs index 97033ddf..99f545ba 100644 --- a/yellowstone-grpc-geyser-messages/src/filter.rs +++ b/yellowstone-grpc-geyser-messages/src/filter.rs @@ -1,8 +1,11 @@ -use std::{ - borrow::Borrow, - collections::HashSet, - sync::Arc, - time::{Duration, Instant}, +use { + std::{ + borrow::Borrow, + collections::HashSet, + sync::Arc, + time::{Duration, Instant}, + }, + tonic::{codec::EncodeBuf, Status}, }; #[derive(Debug, thiserror::Error)] @@ -91,3 +94,14 @@ impl FilterNames { } } } + +#[derive(Debug)] +pub struct Message { + pub filters: Vec, +} + +impl Message { + pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { + todo!() + } +} diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index dc223a91..98bfa3f8 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -14,7 +14,7 @@ use { sync::Arc, }, yellowstone_grpc_geyser_messages::{ - filter::{FilterName, FilterNames}, + filter::{FilterName, FilterNames, Message as FilteredMessage2}, geyser::{ CommitmentLevel, Message, MessageAccount, MessageAccountInfo, MessageBlock, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, @@ -32,10 +32,9 @@ use { SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, - SubscribeRequestFilterTransactions, SubscribeUpdate, SubscribeUpdateAccount, - SubscribeUpdateAccountInfo, SubscribeUpdateBlock, SubscribeUpdateBlockMeta, - SubscribeUpdateEntry, SubscribeUpdatePong, SubscribeUpdateSlot, - SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, + SubscribeRequestFilterTransactions, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, + SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, + SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, SubscribeUpdateTransactionStatus, TransactionError as SubscribeUpdateTransactionError, }, }, @@ -339,31 +338,42 @@ impl Filter { &'a self, message: &'a Message, commitment: Option, - ) -> Box + Send + 'a> { + ) -> Box + Send + 'a> { Box::new( self.get_filters(message, commitment) .filter_map(|(filters, message)| { if filters.is_empty() { None } else { - Some(SubscribeUpdate { - filters: filters - .iter() - .map(|name| name.as_ref().to_string()) - .collect(), - update_oneof: Some(message.as_proto(&self.accounts_data_slice)), - }) + Some(FilteredMessage2 { filters }) + // TODO + // Some(SubscribeUpdate { + // filters: filters + // .iter() + // .map(|name| name.as_ref().to_string()) + // .collect(), + // update_oneof: Some(message.as_proto(&self.accounts_data_slice)), + // }) } }), ) } - pub fn get_pong_msg(&self) -> Option { - self.ping.map(|id| SubscribeUpdate { + pub fn get_pong_msg(&self) -> Option { + self.ping.map(|id| FilteredMessage2 { filters: vec![], - update_oneof: Some(UpdateOneof::Pong(SubscribeUpdatePong { id })), + // TODO + // update_oneof: Some(UpdateOneof::Pong(SubscribeUpdatePong { id })), }) } + + pub const fn create_ping_message() -> FilteredMessage2 { + FilteredMessage2 { + filters: vec![], + // TODO + // update_oneof: Some(UpdateOneof::Ping(SubscribeUpdatePing {})), + } + } } #[derive(Debug, Default, Clone)] diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 15a51d6f..26f7e755 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -5,7 +5,7 @@ use { metrics::{self, DebugClientMessage}, version::GrpcVersionInfo, }, - anyhow::Context, + anyhow::Context as _, log::{error, info}, solana_sdk::{ clock::{Slot, MAX_RECENT_BLOCKHASHES}, @@ -36,19 +36,20 @@ use { }, tonic_health::server::health_reporter, yellowstone_grpc_geyser_messages::{ - filter::FilterNames, + filter::{FilterNames, Message as FilteredMessage}, geyser::{ CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransactionInfo, }, }, - yellowstone_grpc_proto::prelude::{ - geyser_server::{Geyser, GeyserServer}, - subscribe_update::UpdateOneof, - CommitmentLevel as CommitmentLevelProto, GetBlockHeightRequest, GetBlockHeightResponse, - GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, - GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest, IsBlockhashValidResponse, - PingRequest, PongResponse, SubscribeRequest, SubscribeUpdate, SubscribeUpdatePing, + yellowstone_grpc_proto::{ + geyser_weak::geyser_server::{Geyser, GeyserServer}, + prelude::{ + CommitmentLevel as CommitmentLevelProto, GetBlockHeightRequest, GetBlockHeightResponse, + GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, + GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest, + IsBlockhashValidResponse, PingRequest, PongResponse, SubscribeRequest, + }, }, }; @@ -750,7 +751,7 @@ impl GrpcService { async fn client_loop( id: usize, endpoint: String, - stream_tx: mpsc::Sender>, + stream_tx: mpsc::Sender>, mut client_rx: mpsc::UnboundedReceiver>, mut snapshot_rx: Option>>, mut messages_rx: broadcast::Receiver<(CommitmentLevel, Arc>)>, @@ -880,7 +881,7 @@ impl GrpcService { async fn client_loop_snapshot( id: usize, endpoint: &str, - stream_tx: &mpsc::Sender>, + stream_tx: &mpsc::Sender>, client_rx: &mut mpsc::UnboundedReceiver>, snapshot_rx: crossbeam_channel::Receiver>, is_alive: &mut bool, @@ -943,7 +944,7 @@ impl GrpcService { #[tonic::async_trait] impl Geyser for GrpcService { - type SubscribeStream = ReceiverStream>; + type SubscribeStream = ReceiverStream>; async fn subscribe( &self, @@ -973,18 +974,14 @@ impl Geyser for GrpcService { let exit = ping_exit.notified(); tokio::pin!(exit); - let ping_msg = SubscribeUpdate { - filters: vec![], - update_oneof: Some(UpdateOneof::Ping(SubscribeUpdatePing {})), - }; - loop { tokio::select! { _ = &mut exit => { break; } _ = sleep(Duration::from_secs(10)) => { - match ping_stream_tx.try_send(Ok(ping_msg.clone())) { + let msg = Filter::create_ping_message(); + match ping_stream_tx.try_send(Ok(msg)) { Ok(()) => {} Err(mpsc::error::TrySendError::Full(_)) => {} Err(mpsc::error::TrySendError::Closed(_)) => { diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index 260932dd..aeae5409 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -15,10 +15,10 @@ fn main() -> anyhow::Result<()> { .name("subscribe") .route_name("Subscribe") .input_type("crate::geyser::SubscribeRequest") - .output_type("crate::geyser::SubscribeUpdate") - // .output_type("crate::weak::FilteredMessageWithNames") - .codec_path("tonic::codec::ProstCodec") - // .codec_path("crate::geyser_custom::SubscribeCodec") + // .output_type("crate::geyser::SubscribeUpdate") + // .codec_path("tonic::codec::ProstCodec") + .output_type("crate::geyser_weak::FilteredMessage") + .codec_path("crate::geyser_weak::SubscribeCodec") .client_streaming() .server_streaming() .build(), @@ -78,7 +78,19 @@ fn main() -> anyhow::Result<()> { .build(), ) .build(); - Builder::new().compile(&[geyser_service]); + Builder::new() + .build_client(false) + .compile(&[geyser_service]); + + // patching generated custom struct + let mut location = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + location.push("geyser.Geyser.rs"); + let geyser_rs = std::fs::read_to_string(location.clone())?; + let geyser_rs = geyser_rs.replace( + "let codec = crate::geyser_weak::SubscribeCodec::default();", + "let codec = crate::geyser_weak::SubscribeCodec::::default();", + ); + std::fs::write(location, geyser_rs)?; Ok(()) } diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 7ef338f5..72f0d7c5 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -39,6 +39,81 @@ pub mod geyser_weak { #![allow(clippy::missing_const_for_fn)] tonic::include_proto!("geyser.Geyser"); + + use { + prost::Message, + std::marker::PhantomData, + tonic::{ + codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, + Status, + }, + yellowstone_grpc_geyser_messages::filter::Message as FilteredMessage, + }; + + pub struct SubscribeCodec { + _pd: PhantomData<(T, U)>, + } + + impl Default for SubscribeCodec { + fn default() -> Self { + Self { _pd: PhantomData } + } + } + + impl Codec for SubscribeCodec + where + T: Send + 'static, + U: Message + Default + Send + 'static, + { + type Encode = FilteredMessage; + type Decode = U; + + type Encoder = SubscribeEncoder; + type Decoder = ProstDecoder; + + fn encoder(&mut self) -> Self::Encoder { + SubscribeEncoder(PhantomData) + } + + fn decoder(&mut self) -> Self::Decoder { + ProstDecoder(PhantomData) + } + } + + #[derive(Debug)] + pub struct SubscribeEncoder(PhantomData); + + impl Encoder for SubscribeEncoder { + type Item = FilteredMessage; + type Error = Status; + + fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { + item.encode(buf) + } + } + + /// A [`Decoder`] that knows how to decode `U`. + #[derive(Debug)] + pub struct ProstDecoder(PhantomData); + + impl Decoder for ProstDecoder { + type Item = U; + type Error = Status; + + fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { + let item = Message::decode(buf) + .map(Option::Some) + .map_err(from_decode_error)?; + + Ok(item) + } + } + + fn from_decode_error(error: prost::DecodeError) -> Status { + // Map Protobuf parse errors to an INTERNAL status code, as per + // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + Status::internal(error.to_string()) + } } pub mod solana { From 9b8a83f4659d91c387e97fde3bc133eccee20ccc Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 17:13:08 +0200 Subject: [PATCH 04/50] remove dep --- Cargo.lock | 1 - yellowstone-grpc-geyser/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4d76466..cc66a114 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4508,7 +4508,6 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "spl-token-2022", - "thiserror", "tokio", "tokio-stream", "tonic", diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 627303b1..2035ab78 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -40,7 +40,6 @@ solana-logger = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } spl-token-2022 = { workspace = true, features = ["no-entrypoint"] } -thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs"] } tokio-stream = { workspace = true } tonic = { workspace = true, features = ["gzip", "zstd", "tls", "tls-roots"] } From 81e1a600320c1bcdcf50a3f803a133e89e024a40 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 19:46:01 +0200 Subject: [PATCH 05/50] ping/pong encode --- Cargo.lock | 2 + yellowstone-grpc-geyser-messages/Cargo.toml | 2 + .../src/filter.rs | 51 ++++++++++++++++++- yellowstone-grpc-geyser/src/filters.rs | 15 +++--- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc66a114..b5f17eb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4523,6 +4523,8 @@ version = "2.0.0" dependencies = [ "agave-geyser-plugin-interface", "bincode", + "bytes", + "prost", "solana-sdk", "solana-transaction-status", "thiserror", diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml index a36cc54e..898eafae 100644 --- a/yellowstone-grpc-geyser-messages/Cargo.toml +++ b/yellowstone-grpc-geyser-messages/Cargo.toml @@ -13,6 +13,8 @@ publish = { workspace = true } [dependencies] agave-geyser-plugin-interface = { workspace = true } bincode = { workspace = true } +bytes = { workspace = true } +prost = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } thiserror = { workspace = true } diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-geyser-messages/src/filter.rs index 99f545ba..508ddd7c 100644 --- a/yellowstone-grpc-geyser-messages/src/filter.rs +++ b/yellowstone-grpc-geyser-messages/src/filter.rs @@ -1,4 +1,6 @@ use { + bytes::buf::BufMut, + prost::encoding::{encode_key, encode_varint, message, WireType}, std::{ borrow::Borrow, collections::HashSet, @@ -97,11 +99,56 @@ impl FilterNames { #[derive(Debug)] pub struct Message { - pub filters: Vec, + pub filters: Vec, // 1 + pub message: MessageWeak, // 2, 3, 4, 10, 5, 6, 9, 7, 8 } impl Message { pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { - todo!() + for name in self.filters.iter().map(|filter| filter.as_ref()) { + encode_key(1, WireType::LengthDelimited, buf); + encode_varint(name.len() as u64, buf); + buf.put_slice(name.as_bytes()); + } + self.message.encode(buf) } } + +#[derive(Debug)] +pub enum MessageWeak { + Account, // 2 + Slot, // 3 + Transaction, // 4 + TransactionStatus, // 10 + Block, // 5 + Ping, // 6 + Pong(MessageWeakPong), // 9 + BlockMeta, // 7 + Entry, // 8 +} + +impl MessageWeak { + pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { + match self { + MessageWeak::Account => todo!(), + MessageWeak::Slot => todo!(), + MessageWeak::Transaction => todo!(), + MessageWeak::TransactionStatus => todo!(), + MessageWeak::Block => todo!(), + MessageWeak::Ping => encode_key(6, WireType::LengthDelimited, buf), + MessageWeak::Pong(msg) => message::encode(9, &msg, buf), + MessageWeak::BlockMeta => todo!(), + MessageWeak::Entry => todo!(), + } + + Ok(()) + } +} + +#[derive(prost::Message)] +pub struct MessageWeakPong { + #[prost(int32, tag = "1")] + pub id: i32, +} + +// pub struct MessageWeakEntry diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 98bfa3f8..1ff746a0 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -14,7 +14,10 @@ use { sync::Arc, }, yellowstone_grpc_geyser_messages::{ - filter::{FilterName, FilterNames, Message as FilteredMessage2}, + filter::{ + FilterName, FilterNames, Message as FilteredMessage2, + MessageWeak as FilteredMessageWeak, MessageWeakPong as FilteredMessageWeakPong, + }, geyser::{ CommitmentLevel, Message, MessageAccount, MessageAccountInfo, MessageBlock, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, @@ -345,8 +348,8 @@ impl Filter { if filters.is_empty() { None } else { - Some(FilteredMessage2 { filters }) - // TODO + todo!() + // Some(FilteredMessage2 { filters }) // Some(SubscribeUpdate { // filters: filters // .iter() @@ -362,16 +365,14 @@ impl Filter { pub fn get_pong_msg(&self) -> Option { self.ping.map(|id| FilteredMessage2 { filters: vec![], - // TODO - // update_oneof: Some(UpdateOneof::Pong(SubscribeUpdatePong { id })), + message: FilteredMessageWeak::Pong(FilteredMessageWeakPong { id }), }) } pub const fn create_ping_message() -> FilteredMessage2 { FilteredMessage2 { filters: vec![], - // TODO - // update_oneof: Some(UpdateOneof::Ping(SubscribeUpdatePing {})), + message: FilteredMessageWeak::Ping, } } } From c6324c6ecf1a364a9bb15f1b532cae3503ce6b08 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 22:29:12 +0200 Subject: [PATCH 06/50] filter msg wrap --- .../src/filter.rs | 221 ++++++++- yellowstone-grpc-geyser/src/filters.rs | 433 +++++++----------- yellowstone-grpc-geyser/src/grpc.rs | 4 +- 3 files changed, 373 insertions(+), 285 deletions(-) diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-geyser-messages/src/filter.rs index 508ddd7c..5b0fce7e 100644 --- a/yellowstone-grpc-geyser-messages/src/filter.rs +++ b/yellowstone-grpc-geyser-messages/src/filter.rs @@ -1,9 +1,14 @@ use { + crate::geyser::{ + MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, + MessageTransaction, MessageTransactionInfo, + }, bytes::buf::BufMut, prost::encoding::{encode_key, encode_varint, message, WireType}, std::{ borrow::Borrow, collections::HashSet, + ops::Range, sync::Arc, time::{Duration, Instant}, }, @@ -104,6 +109,10 @@ pub struct Message { } impl Message { + pub fn new(filters: Vec, message: MessageWeak) -> Self { + Self { filters, message } + } + pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { for name in self.filters.iter().map(|filter| filter.as_ref()) { encode_key(1, WireType::LengthDelimited, buf); @@ -116,18 +125,50 @@ impl Message { #[derive(Debug)] pub enum MessageWeak { - Account, // 2 - Slot, // 3 - Transaction, // 4 - TransactionStatus, // 10 - Block, // 5 - Ping, // 6 - Pong(MessageWeakPong), // 9 - BlockMeta, // 7 - Entry, // 8 + Account, // 2 + Slot, // 3 + Transaction, // 4 + TransactionStatus, // 10 + Block, // 5 + Ping, // 6 + Pong(MessageWeakPong), // 9 + BlockMeta, // 7 + Entry(MessageWeakEntry), // 8 } impl MessageWeak { + pub fn account(message: &MessageAccount, accounts_data_slice: Vec>) -> Self { + todo!() + } + + pub fn slot(message: MessageSlot) -> Self { + todo!() + } + + pub fn transaction(message: &MessageTransaction) -> Self { + todo!() + } + + pub fn transaction_status(message: &MessageTransaction) -> Self { + todo!() + } + + pub fn block(message: MessageWeakBlock) -> Self { + todo!() + } + + pub const fn pong(id: i32) -> Self { + Self::Pong(MessageWeakPong { id }) + } + + pub fn block_meta(message: &Arc) -> Self { + todo!() + } + + pub fn entry(message: &Arc) -> Self { + Self::Entry(MessageWeakEntry(Arc::clone(message))) + } + pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { match self { MessageWeak::Account => todo!(), @@ -138,17 +179,175 @@ impl MessageWeak { MessageWeak::Ping => encode_key(6, WireType::LengthDelimited, buf), MessageWeak::Pong(msg) => message::encode(9, &msg, buf), MessageWeak::BlockMeta => todo!(), - MessageWeak::Entry => todo!(), + MessageWeak::Entry(msg) => todo!(), } Ok(()) } } +#[derive(Debug)] +pub struct MessageWeakBlock { + pub meta: Arc, + pub transactions: Vec>, + pub updated_account_count: u64, + pub accounts: Vec>, + pub accounts_data_slice: Vec>, + pub entries: Vec>, +} + #[derive(prost::Message)] pub struct MessageWeakPong { #[prost(int32, tag = "1")] pub id: i32, } -// pub struct MessageWeakEntry +#[derive(Debug)] +pub struct MessageWeakEntry(Arc); + +// #[derive(Debug, Clone)] +// pub enum FilteredMessage2<'a> { +// Slot(&'a MessageSlot), +// Account(&'a MessageAccount), +// Transaction(&'a MessageTransaction), +// TransactionStatus(&'a MessageTransaction), +// Entry(&'a MessageEntry), +// Block(MessageBlock), +// BlockMeta(&'a MessageBlockMeta), +// } + +// impl<'a> FilteredMessage2<'a> { +// fn as_proto_account( +// message: &MessageAccountInfo, +// accounts_data_slice: &[Range], +// ) -> SubscribeUpdateAccountInfo { +// let data = if accounts_data_slice.is_empty() { +// message.data.clone() +// } else { +// let mut data = +// Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); +// for slice in accounts_data_slice { +// if message.data.len() >= slice.end { +// data.extend_from_slice(&message.data[slice.start..slice.end]); +// } +// } +// data +// }; +// SubscribeUpdateAccountInfo { +// pubkey: message.pubkey.as_ref().into(), +// lamports: message.lamports, +// owner: message.owner.as_ref().into(), +// executable: message.executable, +// rent_epoch: message.rent_epoch, +// data, +// write_version: message.write_version, +// txn_signature: message.txn_signature.map(|s| s.as_ref().into()), +// } +// } + +// fn as_proto_transaction(message: &MessageTransactionInfo) -> SubscribeUpdateTransactionInfo { +// SubscribeUpdateTransactionInfo { +// signature: message.signature.as_ref().into(), +// is_vote: message.is_vote, +// transaction: Some(convert_to::create_transaction(&message.transaction)), +// meta: Some(convert_to::create_transaction_meta(&message.meta)), +// index: message.index as u64, +// } +// } + +// fn as_proto_entry(message: &MessageEntry) -> SubscribeUpdateEntry { +// SubscribeUpdateEntry { +// slot: message.slot, +// index: message.index as u64, +// num_hashes: message.num_hashes, +// hash: message.hash.into(), +// executed_transaction_count: message.executed_transaction_count, +// starting_transaction_index: message.starting_transaction_index, +// } +// } + +// pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { +// match self { +// Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { +// slot: message.slot, +// parent: message.parent, +// status: message.status as i32, +// }), +// Self::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount { +// account: Some(Self::as_proto_account( +// message.account.as_ref(), +// accounts_data_slice, +// )), +// slot: message.slot, +// is_startup: message.is_startup, +// }), +// Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction { +// transaction: Some(Self::as_proto_transaction(message.transaction.as_ref())), +// slot: message.slot, +// }), +// Self::TransactionStatus(message) => { +// UpdateOneof::TransactionStatus(SubscribeUpdateTransactionStatus { +// slot: message.slot, +// signature: message.transaction.signature.as_ref().into(), +// is_vote: message.transaction.is_vote, +// index: message.transaction.index as u64, +// err: match &message.transaction.meta.status { +// Ok(()) => None, +// Err(err) => Some(SubscribeUpdateTransactionError { +// err: bincode::serialize(&err) +// .expect("transaction error to serialize to bytes"), +// }), +// }, +// }) +// } +// Self::Entry(message) => UpdateOneof::Entry(Self::as_proto_entry(message)), +// Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { +// slot: message.meta.slot, +// blockhash: message.meta.blockhash.clone(), +// rewards: Some(convert_to::create_rewards_obj( +// message.meta.rewards.as_slice(), +// message.meta.num_partitions, +// )), +// block_time: message.meta.block_time.map(convert_to::create_timestamp), +// block_height: message +// .meta +// .block_height +// .map(convert_to::create_block_height), +// parent_slot: message.meta.parent_slot, +// parent_blockhash: message.meta.parent_blockhash.clone(), +// executed_transaction_count: message.meta.executed_transaction_count, +// transactions: message +// .transactions +// .iter() +// .map(|tx| Self::as_proto_transaction(tx.as_ref())) +// .collect(), +// updated_account_count: message.updated_account_count, +// accounts: message +// .accounts +// .iter() +// .map(|acc| Self::as_proto_account(acc.as_ref(), accounts_data_slice)) +// .collect(), +// entries_count: message.meta.entries_count, +// entries: message +// .entries +// .iter() +// .map(|entry| Self::as_proto_entry(entry.as_ref())) +// .collect(), +// }), +// Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { +// slot: message.slot, +// blockhash: message.blockhash.clone(), +// rewards: Some(convert_to::create_rewards_obj( +// message.rewards.as_slice(), +// message.num_partitions, +// )), +// 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, +// entries_count: message.entries_count, +// }), +// } +// } +// } diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 1ff746a0..b19068e2 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -8,6 +8,7 @@ use { solana_sdk::{pubkey::Pubkey, signature::Signature}, spl_token_2022::{generic_token_account::GenericTokenAccount, state::Account as TokenAccount}, std::{ + borrow::Cow, collections::{HashMap, HashSet}, ops::Range, str::FromStr, @@ -15,177 +16,80 @@ use { }, yellowstone_grpc_geyser_messages::{ filter::{ - FilterName, FilterNames, Message as FilteredMessage2, - MessageWeak as FilteredMessageWeak, MessageWeakPong as FilteredMessageWeakPong, + FilterName, FilterNames, Message as FilteredMessage, + MessageWeak as FilteredMessageWeak, MessageWeakBlock as FilteredMessageWeakBlock, }, geyser::{ - CommitmentLevel, Message, MessageAccount, MessageAccountInfo, MessageBlock, - MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, - MessageTransactionInfo, + CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, + MessageSlot, MessageTransaction, }, }, - yellowstone_grpc_proto::{ - convert_to, - prelude::{ - subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, - subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, - subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, - subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, - SubscribeRequest, SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, - SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, - SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, - SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, - SubscribeRequestFilterTransactions, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, - SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, - SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, - SubscribeUpdateTransactionStatus, TransactionError as SubscribeUpdateTransactionError, - }, + yellowstone_grpc_proto::prelude::{ + subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, + subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, + subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, + CommitmentLevel as CommitmentLevelProto, SubscribeRequest, + SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, + SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, + SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, + SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, + SubscribeRequestFilterTransactions, }, }; -#[derive(Debug, Clone)] -pub enum FilteredMessage<'a> { - Slot(&'a MessageSlot), - Account(&'a MessageAccount), - Transaction(&'a MessageTransaction), - TransactionStatus(&'a MessageTransaction), - Entry(&'a MessageEntry), - Block(MessageBlock), - BlockMeta(&'a MessageBlockMeta), +pub enum FilteredMessages<'a> { + Once(Option), + Pair((Option, Option)), + Boxed(Box + Send + 'a>), } -impl<'a> FilteredMessage<'a> { - fn as_proto_account( - message: &MessageAccountInfo, - accounts_data_slice: &[Range], - ) -> SubscribeUpdateAccountInfo { - let data = if accounts_data_slice.is_empty() { - message.data.clone() +impl FilteredMessages<'_> { + fn wrap(filters: Cow<'_, [FilterName]>, msg: FilteredMessageWeak) -> Option { + if filters.is_empty() { + None } else { - let mut data = - Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); - for slice in accounts_data_slice { - if message.data.len() >= slice.end { - data.extend_from_slice(&message.data[slice.start..slice.end]); - } - } - data - }; - SubscribeUpdateAccountInfo { - pubkey: message.pubkey.as_ref().into(), - lamports: message.lamports, - owner: message.owner.as_ref().into(), - executable: message.executable, - rent_epoch: message.rent_epoch, - data, - write_version: message.write_version, - txn_signature: message.txn_signature.map(|s| s.as_ref().into()), + Some(FilteredMessage::new(filters.into_owned(), msg)) } } - fn as_proto_transaction(message: &MessageTransactionInfo) -> SubscribeUpdateTransactionInfo { - SubscribeUpdateTransactionInfo { - signature: message.signature.as_ref().into(), - is_vote: message.is_vote, - transaction: Some(convert_to::create_transaction(&message.transaction)), - meta: Some(convert_to::create_transaction_meta(&message.meta)), - index: message.index as u64, - } + pub fn once(filters: Cow<'_, [FilterName]>, msg: FilteredMessageWeak) -> Self { + Self::Once(Self::wrap(filters, msg)) } - fn as_proto_entry(message: &MessageEntry) -> SubscribeUpdateEntry { - SubscribeUpdateEntry { - slot: message.slot, - index: message.index as u64, - num_hashes: message.num_hashes, - hash: message.hash.into(), - executed_transaction_count: message.executed_transaction_count, - starting_transaction_index: message.starting_transaction_index, + pub fn pair( + item1: (Vec, FilteredMessageWeak), + item2: (Vec, FilteredMessageWeak), + ) -> Self { + Self::Pair(( + Self::wrap(item1.0.into(), item1.1), + Self::wrap(item2.0.into(), item2.1), + )) + } +} + +impl<'a> Iterator for FilteredMessages<'a> { + type Item = FilteredMessage; + + fn next(&mut self) -> Option { + match self { + Self::Once(msg) => msg.take(), + Self::Pair((item1, item2)) => item1.take().or_else(|| item2.take()), + Self::Boxed(it) => it.next(), } } - pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { + fn size_hint(&self) -> (usize, Option) { match self { - Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { - slot: message.slot, - parent: message.parent, - status: message.status as i32, - }), - Self::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount { - account: Some(Self::as_proto_account( - message.account.as_ref(), - accounts_data_slice, - )), - slot: message.slot, - is_startup: message.is_startup, - }), - Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction { - transaction: Some(Self::as_proto_transaction(message.transaction.as_ref())), - slot: message.slot, - }), - Self::TransactionStatus(message) => { - UpdateOneof::TransactionStatus(SubscribeUpdateTransactionStatus { - slot: message.slot, - signature: message.transaction.signature.as_ref().into(), - is_vote: message.transaction.is_vote, - index: message.transaction.index as u64, - err: match &message.transaction.meta.status { - Ok(()) => None, - Err(err) => Some(SubscribeUpdateTransactionError { - err: bincode::serialize(&err) - .expect("transaction error to serialize to bytes"), - }), - }, - }) + Self::Once(msg) => { + let total = if msg.is_some() { 1 } else { 0 }; + (total, Some(total)) } - Self::Entry(message) => UpdateOneof::Entry(Self::as_proto_entry(message)), - Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { - slot: message.meta.slot, - blockhash: message.meta.blockhash.clone(), - rewards: Some(convert_to::create_rewards_obj( - message.meta.rewards.as_slice(), - message.meta.num_partitions, - )), - block_time: message.meta.block_time.map(convert_to::create_timestamp), - block_height: message - .meta - .block_height - .map(convert_to::create_block_height), - parent_slot: message.meta.parent_slot, - parent_blockhash: message.meta.parent_blockhash.clone(), - executed_transaction_count: message.meta.executed_transaction_count, - transactions: message - .transactions - .iter() - .map(|tx| Self::as_proto_transaction(tx.as_ref())) - .collect(), - updated_account_count: message.updated_account_count, - accounts: message - .accounts - .iter() - .map(|acc| Self::as_proto_account(acc.as_ref(), accounts_data_slice)) - .collect(), - entries_count: message.meta.entries_count, - entries: message - .entries - .iter() - .map(|entry| Self::as_proto_entry(entry.as_ref())) - .collect(), - }), - Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { - slot: message.slot, - blockhash: message.blockhash.clone(), - rewards: Some(convert_to::create_rewards_obj( - message.rewards.as_slice(), - message.num_partitions, - )), - 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, - entries_count: message.entries_count, - }), + Self::Pair((item1, item2)) => { + let total = + if item1.is_some() { 1 } else { 0 } + if item2.is_some() { 1 } else { 0 }; + (total, Some(total)) + } + Self::Boxed(it) => it.size_hint(), } } } @@ -196,7 +100,7 @@ pub struct Filter { slots: FilterSlots, transactions: FilterTransactions, transactions_status: FilterTransactions, - entry: FilterEntry, + entries: FilterEntries, blocks: FilterBlocks, blocks_meta: FilterBlocksMeta, commitment: CommitmentLevel, @@ -217,7 +121,7 @@ impl Default for Filter { filter_type: FilterTransactionsType::TransactionStatus, filters: HashMap::new(), }, - entry: FilterEntry::default(), + entries: FilterEntries::default(), blocks: FilterBlocks::default(), blocks_meta: FilterBlocksMeta::default(), commitment: CommitmentLevel::Processed, @@ -248,7 +152,7 @@ impl Filter { FilterTransactionsType::TransactionStatus, names, )?, - entry: FilterEntry::new(&config.entry, &limit.entry, names)?, + entries: FilterEntries::new(&config.entry, &limit.entry, names)?, blocks: FilterBlocks::new(&config.blocks, &limit.blocks, names)?, blocks_meta: FilterBlocksMeta::new(&config.blocks_meta, &limit.blocks_meta, names)?, commitment: Self::decode_commitment(config.commitment)?, @@ -298,7 +202,7 @@ impl Filter { "transactions_status", self.transactions_status.filters.len(), ), - ("entry", self.entry.filters.len()), + ("entry", self.entries.filters.len()), ("blocks", self.blocks.filters.len()), ("blocks_meta", self.blocks_meta.filters.len()), ( @@ -307,7 +211,7 @@ impl Filter { + self.slots.filters.len() + self.transactions.filters.len() + self.transactions_status.filters.len() - + self.entry.filters.len() + + self.entries.filters.len() + self.blocks.filters.len() + self.blocks_meta.filters.len(), ), @@ -322,55 +226,31 @@ impl Filter { &'a self, message: &'a Message, commitment: Option, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { + ) -> FilteredMessages<'a> { match message { - Message::Account(message) => self.accounts.get_filters(message), + Message::Account(message) => self + .accounts + .get_filters(message, &self.accounts_data_slice), Message::Slot(message) => self.slots.get_filters(message, commitment), - Message::Transaction(message) => Box::new( - self.transactions - .get_filters(message) - .chain(self.transactions_status.get_filters(message)), + Message::Transaction(message) => FilteredMessages::pair( + self.transactions.get_filters(message), + self.transactions_status.get_filters(message), ), - Message::Entry(message) => self.entry.get_filters(message), - Message::Block(message) => self.blocks.get_filters(message), + Message::Entry(message) => self.entries.get_filters(message), + Message::Block(message) => self.blocks.get_filters(message, &self.accounts_data_slice), Message::BlockMeta(message) => self.blocks_meta.get_filters(message), } } - pub fn get_update<'a>( - &'a self, - message: &'a Message, - commitment: Option, - ) -> Box + Send + 'a> { - Box::new( - self.get_filters(message, commitment) - .filter_map(|(filters, message)| { - if filters.is_empty() { - None - } else { - todo!() - // Some(FilteredMessage2 { filters }) - // Some(SubscribeUpdate { - // filters: filters - // .iter() - // .map(|name| name.as_ref().to_string()) - // .collect(), - // update_oneof: Some(message.as_proto(&self.accounts_data_slice)), - // }) - } - }), - ) - } - - pub fn get_pong_msg(&self) -> Option { - self.ping.map(|id| FilteredMessage2 { + pub fn get_pong_msg(&self) -> Option { + self.ping.map(|id| FilteredMessage { filters: vec![], - message: FilteredMessageWeak::Pong(FilteredMessageWeakPong { id }), + message: FilteredMessageWeak::pong(id), }) } - pub const fn create_ping_message() -> FilteredMessage2 { - FilteredMessage2 { + pub const fn create_ping_message() -> FilteredMessage { + FilteredMessage { filters: vec![], message: FilteredMessageWeak::Ping, } @@ -454,19 +334,20 @@ impl FilterAccounts { Ok(required) } - fn get_filters<'a>( - &'a self, - message: &'a MessageAccount, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { + fn get_filters( + &self, + message: &MessageAccount, + accounts_data_slice: &[Range], + ) -> FilteredMessages { let mut filter = FilterAccountsMatch::new(self); filter.match_txn_signature(&message.account.txn_signature); filter.match_account(&message.account.pubkey); filter.match_owner(&message.account.owner); filter.match_data_lamports(&message.account.data, message.account.lamports); - Box::new(std::iter::once(( - filter.get_filters(), - FilteredMessage::Account(message), - ))) + FilteredMessages::once( + filter.get_filters().into(), + FilteredMessageWeak::account(message, accounts_data_slice.to_vec()), + ) } } @@ -725,24 +606,24 @@ impl FilterSlots { }) } - fn get_filters<'a>( - &'a self, - message: &'a MessageSlot, + fn get_filters( + &self, + message: &MessageSlot, commitment: Option, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { - Box::new(std::iter::once(( - self.filters - .iter() - .filter_map(|(name, inner)| { - if !inner.filter_by_commitment || commitment == Some(message.status) { - Some(name.clone()) - } else { - None - } - }) - .collect(), - FilteredMessage::Slot(message), - ))) + ) -> FilteredMessages { + let filters = self + .filters + .iter() + .filter_map(|(name, inner)| { + if !inner.filter_by_commitment || commitment == Some(message.status) { + Some(name.clone()) + } else { + None + } + }) + .collect::>(); + + FilteredMessages::once(filters.into(), FilteredMessageWeak::slot(*message)) } } @@ -835,10 +716,10 @@ impl FilterTransactions { }) } - pub fn get_filters<'a>( - &'a self, - message: &'a MessageTransaction, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { + pub fn get_filters( + &self, + message: &MessageTransaction, + ) -> (Vec, FilteredMessageWeak) { let filters = self .filters .iter() @@ -912,22 +793,24 @@ impl FilterTransactions { Some(name.clone()) }) .collect(); + let message = match self.filter_type { - FilterTransactionsType::Transaction => FilteredMessage::Transaction(message), + FilterTransactionsType::Transaction => FilteredMessageWeak::transaction(message), FilterTransactionsType::TransactionStatus => { - FilteredMessage::TransactionStatus(message) + FilteredMessageWeak::transaction_status(message) } }; - Box::new(std::iter::once((filters, message))) + + (filters, message) } } #[derive(Debug, Default, Clone)] -struct FilterEntry { +struct FilterEntries { filters: Vec, } -impl FilterEntry { +impl FilterEntries { fn new( configs: &HashMap, limit: &ConfigGrpcFiltersEntry, @@ -943,14 +826,11 @@ impl FilterEntry { }) } - fn get_filters<'a>( - &'a self, - message: &'a MessageEntry, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { - Box::new(std::iter::once(( - self.filters.clone(), - FilteredMessage::Entry(message), - ))) + fn get_filters(&self, message: &Arc) -> FilteredMessages { + FilteredMessages::once( + self.filters.as_slice().into(), + FilteredMessageWeak::entry(message), + ) } } @@ -1016,9 +896,10 @@ impl FilterBlocks { fn get_filters<'a>( &'a self, - message: &'a MessageBlock, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { - Box::new(self.filters.iter().map(move |(filter, inner)| { + message: &'a Arc, + accounts_data_slice: &'a [Range], + ) -> FilteredMessages<'a> { + FilteredMessages::Boxed(Box::new(self.filters.iter().map(move |(filter, inner)| { #[allow(clippy::unnecessary_filter_map)] let transactions = if matches!(inner.include_transactions, None | Some(true)) { message @@ -1071,17 +952,18 @@ impl FilterBlocks { vec![] }; - ( + FilteredMessage::new( vec![filter.clone()], - FilteredMessage::Block(MessageBlock { + FilteredMessageWeak::block(FilteredMessageWeakBlock { meta: Arc::clone(&message.meta), transactions, updated_account_count: message.updated_account_count, + accounts_data_slice: accounts_data_slice.to_vec(), accounts, entries, }), ) - })) + }))) } } @@ -1106,14 +988,11 @@ impl FilterBlocksMeta { }) } - fn get_filters<'a>( - &'a self, - message: &'a MessageBlockMeta, - ) -> Box, FilteredMessage<'a>)> + Send + 'a> { - Box::new(std::iter::once(( - self.filters.clone(), - FilteredMessage::BlockMeta(message), - ))) + fn get_filters(&self, message: &Arc) -> FilteredMessages { + FilteredMessages::once( + self.filters.as_slice().into(), + FilteredMessageWeak::block_meta(message), + ) } } @@ -1151,7 +1030,7 @@ impl FilterAccountsDataSlice { #[cfg(test)] mod tests { use { - super::{FilterName, FilterNames, FilteredMessage}, + super::{FilterName, FilterNames}, crate::{config::ConfigGrpcFilters, filters::Filter}, solana_sdk::{ hash::Hash, @@ -1162,8 +1041,9 @@ mod tests { }, solana_transaction_status::TransactionStatusMeta, std::{collections::HashMap, sync::Arc, time::Duration}, - yellowstone_grpc_geyser_messages::geyser::{ - Message, MessageTransaction, MessageTransactionInfo, + yellowstone_grpc_geyser_messages::{ + filter::MessageWeak as FilteredMessageWeak, + geyser::{Message, MessageTransaction, MessageTransactionInfo}, }, yellowstone_grpc_proto::geyser::{ SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterTransactions, @@ -1380,12 +1260,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None).collect::>(); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].0, vec![FilterName::new("serum")]); - assert!(matches!(updates[0].1, FilteredMessage::Transaction(_))); - assert_eq!(updates[1].0, Vec::::new()); + assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); + assert!(matches!( + updates[0].message, + FilteredMessageWeak::Transaction + )); + assert_eq!(updates[1].filters, Vec::::new()); assert!(matches!( - updates[1].1, - FilteredMessage::TransactionStatus(_) + updates[1].message, + FilteredMessageWeak::TransactionStatus )); } @@ -1430,12 +1313,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None).collect::>(); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].0, vec![FilterName::new("serum")]); - assert!(matches!(updates[0].1, FilteredMessage::Transaction(_))); - assert_eq!(updates[1].0, Vec::::new()); + assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); assert!(matches!( - updates[1].1, - FilteredMessage::TransactionStatus(_) + updates[0].message, + FilteredMessageWeak::Transaction + )); + assert_eq!(updates[1].filters, Vec::::new()); + assert!(matches!( + updates[1].message, + FilteredMessageWeak::TransactionStatus )); } @@ -1478,8 +1364,8 @@ mod tests { let message_transaction = create_message_transaction(&keypair_b, vec![account_key_b, account_key_a]); let message = Message::Transaction(message_transaction); - for (filters, _message) in filter.get_filters(&message, None) { - assert!(filters.is_empty()); + for message in filter.get_filters(&message, None) { + assert!(message.filters.is_empty()); } } @@ -1532,12 +1418,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None).collect::>(); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].0, vec![FilterName::new("serum")]); - assert!(matches!(updates[0].1, FilteredMessage::Transaction(_))); - assert_eq!(updates[1].0, Vec::::new()); + assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); + assert!(matches!( + updates[0].message, + FilteredMessageWeak::Transaction + )); + assert_eq!(updates[1].filters, Vec::::new()); assert!(matches!( - updates[1].1, - FilteredMessage::TransactionStatus(_) + updates[1].message, + FilteredMessageWeak::TransactionStatus )); } @@ -1586,8 +1475,8 @@ mod tests { let message_transaction = create_message_transaction(&keypair_x, vec![account_key_x, account_key_z]); let message = Message::Transaction(message_transaction); - for (filters, _message) in filter.get_filters(&message, None) { - assert!(filters.is_empty()); + for message in filter.get_filters(&message, None) { + assert!(message.filters.is_empty()); } } } diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 26f7e755..6207f5e3 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -840,7 +840,7 @@ impl GrpcService { if commitment == filter.get_commitment_level() { for message in messages.iter() { - for message in filter.get_update(message, Some(commitment)) { + for message in filter.get_filters(message, Some(commitment)) { match stream_tx.try_send(Ok(message)) { Ok(()) => {} Err(mpsc::error::TrySendError::Full(_)) => { @@ -931,7 +931,7 @@ impl GrpcService { } }; - for message in filter.get_update(&message, None) { + for message in filter.get_filters(&message, None) { if stream_tx.send(Ok(message)).await.is_err() { error!("client #{id}: stream closed"); *is_alive = false; From e5a7cff88c3dc43770eb5e4bdaa6287e227f9dd5 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 5 Nov 2024 22:29:48 +0200 Subject: [PATCH 07/50] geyser: wrap messages in Arc --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 069b6585..66f3f161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The minor version will be incremented upon a breaking change and the patch versi - node: remove generated grpc files ([#447](https://github.com/rpcpool/yellowstone-grpc/pull/447)) - proto: add txn_signature filter ([#445](https://github.com/rpcpool/yellowstone-grpc/pull/445)) - geyser: limit length of filter name ([#448](https://github.com/rpcpool/yellowstone-grpc/pull/448)) +- geyser: wrap messages in Arc ([#449](https://github.com/rpcpool/yellowstone-grpc/pull/449)) ### Breaking From 6c6ab2cce77145552d48a0f114867a6ba6cfac60 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 09:01:48 +0200 Subject: [PATCH 08/50] remove extra calls --- yellowstone-grpc-geyser/src/filters.rs | 46 ++++++++++++++++---------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index b19068e2..7dc5c3d5 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -43,6 +43,26 @@ pub enum FilteredMessages<'a> { Boxed(Box + Send + 'a>), } +macro_rules! filtered_messages_once_owned { + ($filters:ident, $message:expr) => { + FilteredMessages::Once(if $filters.is_empty() { + None + } else { + Some(FilteredMessage::new($filters, $message)) + }) + }; +} + +macro_rules! filtered_messages_once_ref { + ($filters:ident, $message:expr) => { + FilteredMessages::Once(if $filters.is_empty() { + None + } else { + Some(FilteredMessage::new($filters.to_vec(), $message)) + }) + }; +} + impl FilteredMessages<'_> { fn wrap(filters: Cow<'_, [FilterName]>, msg: FilteredMessageWeak) -> Option { if filters.is_empty() { @@ -52,10 +72,6 @@ impl FilteredMessages<'_> { } } - pub fn once(filters: Cow<'_, [FilterName]>, msg: FilteredMessageWeak) -> Self { - Self::Once(Self::wrap(filters, msg)) - } - pub fn pair( item1: (Vec, FilteredMessageWeak), item2: (Vec, FilteredMessageWeak), @@ -344,9 +360,10 @@ impl FilterAccounts { filter.match_account(&message.account.pubkey); filter.match_owner(&message.account.owner); filter.match_data_lamports(&message.account.data, message.account.lamports); - FilteredMessages::once( - filter.get_filters().into(), - FilteredMessageWeak::account(message, accounts_data_slice.to_vec()), + let filters = filter.get_filters(); + filtered_messages_once_owned!( + filters, + FilteredMessageWeak::account(message, accounts_data_slice.to_vec()) ) } } @@ -622,8 +639,7 @@ impl FilterSlots { } }) .collect::>(); - - FilteredMessages::once(filters.into(), FilteredMessageWeak::slot(*message)) + filtered_messages_once_owned!(filters, FilteredMessageWeak::slot(*message)) } } @@ -827,10 +843,8 @@ impl FilterEntries { } fn get_filters(&self, message: &Arc) -> FilteredMessages { - FilteredMessages::once( - self.filters.as_slice().into(), - FilteredMessageWeak::entry(message), - ) + let filters = self.filters.as_slice(); + filtered_messages_once_ref!(filters, FilteredMessageWeak::entry(message)) } } @@ -989,10 +1003,8 @@ impl FilterBlocksMeta { } fn get_filters(&self, message: &Arc) -> FilteredMessages { - FilteredMessages::once( - self.filters.as_slice().into(), - FilteredMessageWeak::block_meta(message), - ) + let filters = self.filters.as_slice(); + filtered_messages_once_ref!(filters, FilteredMessageWeak::block_meta(message)) } } From 0fb8c963402dde0ee178a5287fccfab0c73634d8 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 09:34:19 +0200 Subject: [PATCH 09/50] rm iter --- Cargo.lock | 1 + Cargo.toml | 1 + yellowstone-grpc-geyser/Cargo.toml | 1 + yellowstone-grpc-geyser/src/filters.rs | 146 ++++++++----------------- 4 files changed, 51 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5f17eb3..7536fe50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4504,6 +4504,7 @@ dependencies = [ "prometheus", "serde", "serde_json", + "smallvec", "solana-logger", "solana-sdk", "solana-transaction-status", diff --git a/Cargo.toml b/Cargo.toml index 1e4f6d26..2317b49c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ solana-account-decoder = "~2.0.10" solana-logger = "~2.0.10" solana-sdk = "~2.0.10" solana-transaction-status = "~2.0.10" +smallvec = "1.13.2" spl-token-2022 = "4.0.0" thiserror = "1.0" tokio = "1.21.2" diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 2035ab78..73567794 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -39,6 +39,7 @@ serde_json = { workspace = true } solana-logger = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } +smallvec = { workspace = true } spl-token-2022 = { workspace = true, features = ["no-entrypoint"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs"] } tokio-stream = { workspace = true } diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 7dc5c3d5..ecacb89a 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -5,10 +5,10 @@ use { ConfigGrpcFiltersTransactions, }, base64::{engine::general_purpose::STANDARD as base64_engine, Engine}, + smallvec::SmallVec, solana_sdk::{pubkey::Pubkey, signature::Signature}, spl_token_2022::{generic_token_account::GenericTokenAccount, state::Account as TokenAccount}, std::{ - borrow::Cow, collections::{HashMap, HashSet}, ops::Range, str::FromStr, @@ -37,77 +37,26 @@ use { }, }; -pub enum FilteredMessages<'a> { - Once(Option), - Pair((Option, Option)), - Boxed(Box + Send + 'a>), -} +pub type FilteredMessages = SmallVec<[FilteredMessage; 8]>; macro_rules! filtered_messages_once_owned { - ($filters:ident, $message:expr) => { - FilteredMessages::Once(if $filters.is_empty() { - None - } else { - Some(FilteredMessage::new($filters, $message)) - }) - }; + ($filters:ident, $message:expr) => {{ + let mut messages = FilteredMessages::new(); + if !$filters.is_empty() { + messages.push(FilteredMessage::new($filters, $message)); + } + messages + }}; } macro_rules! filtered_messages_once_ref { - ($filters:ident, $message:expr) => { - FilteredMessages::Once(if $filters.is_empty() { - None - } else { - Some(FilteredMessage::new($filters.to_vec(), $message)) - }) - }; -} - -impl FilteredMessages<'_> { - fn wrap(filters: Cow<'_, [FilterName]>, msg: FilteredMessageWeak) -> Option { - if filters.is_empty() { - None - } else { - Some(FilteredMessage::new(filters.into_owned(), msg)) + ($filters:ident, $message:expr) => {{ + let mut messages = FilteredMessages::new(); + if !$filters.is_empty() { + messages.push(FilteredMessage::new($filters.to_vec(), $message)); } - } - - pub fn pair( - item1: (Vec, FilteredMessageWeak), - item2: (Vec, FilteredMessageWeak), - ) -> Self { - Self::Pair(( - Self::wrap(item1.0.into(), item1.1), - Self::wrap(item2.0.into(), item2.1), - )) - } -} - -impl<'a> Iterator for FilteredMessages<'a> { - type Item = FilteredMessage; - - fn next(&mut self) -> Option { - match self { - Self::Once(msg) => msg.take(), - Self::Pair((item1, item2)) => item1.take().or_else(|| item2.take()), - Self::Boxed(it) => it.next(), - } - } - - fn size_hint(&self) -> (usize, Option) { - match self { - Self::Once(msg) => { - let total = if msg.is_some() { 1 } else { 0 }; - (total, Some(total)) - } - Self::Pair((item1, item2)) => { - let total = - if item1.is_some() { 1 } else { 0 } + if item2.is_some() { 1 } else { 0 }; - (total, Some(total)) - } - Self::Boxed(it) => it.size_hint(), - } - } + messages + }}; } #[derive(Debug, Clone)] @@ -238,20 +187,21 @@ impl Filter { self.commitment } - pub fn get_filters<'a>( - &'a self, - message: &'a Message, + pub fn get_filters( + &self, + message: &Message, commitment: Option, - ) -> FilteredMessages<'a> { + ) -> FilteredMessages { match message { Message::Account(message) => self .accounts .get_filters(message, &self.accounts_data_slice), Message::Slot(message) => self.slots.get_filters(message, commitment), - Message::Transaction(message) => FilteredMessages::pair( - self.transactions.get_filters(message), - self.transactions_status.get_filters(message), - ), + Message::Transaction(message) => { + let mut messages = self.transactions.get_filters(message); + messages.append(&mut self.transactions_status.get_filters(message)); + messages + } Message::Entry(message) => self.entries.get_filters(message), Message::Block(message) => self.blocks.get_filters(message, &self.accounts_data_slice), Message::BlockMeta(message) => self.blocks_meta.get_filters(message), @@ -732,10 +682,7 @@ impl FilterTransactions { }) } - pub fn get_filters( - &self, - message: &MessageTransaction, - ) -> (Vec, FilteredMessageWeak) { + pub fn get_filters(&self, message: &MessageTransaction) -> FilteredMessages { let filters = self .filters .iter() @@ -808,16 +755,17 @@ impl FilterTransactions { Some(name.clone()) }) - .collect(); + .collect::>(); - let message = match self.filter_type { - FilterTransactionsType::Transaction => FilteredMessageWeak::transaction(message), - FilterTransactionsType::TransactionStatus => { - FilteredMessageWeak::transaction_status(message) + filtered_messages_once_owned!( + filters, + match self.filter_type { + FilterTransactionsType::Transaction => FilteredMessageWeak::transaction(message), + FilterTransactionsType::TransactionStatus => { + FilteredMessageWeak::transaction_status(message) + } } - }; - - (filters, message) + ) } } @@ -908,12 +856,13 @@ impl FilterBlocks { Ok(this) } - fn get_filters<'a>( - &'a self, - message: &'a Arc, - accounts_data_slice: &'a [Range], - ) -> FilteredMessages<'a> { - FilteredMessages::Boxed(Box::new(self.filters.iter().map(move |(filter, inner)| { + fn get_filters( + &self, + message: &Arc, + accounts_data_slice: &[Range], + ) -> FilteredMessages { + let mut messages = FilteredMessages::new(); + for (filter, inner) in self.filters.iter() { #[allow(clippy::unnecessary_filter_map)] let transactions = if matches!(inner.include_transactions, None | Some(true)) { message @@ -966,7 +915,7 @@ impl FilterBlocks { vec![] }; - FilteredMessage::new( + messages.push(FilteredMessage::new( vec![filter.clone()], FilteredMessageWeak::block(FilteredMessageWeakBlock { meta: Arc::clone(&message.meta), @@ -976,8 +925,9 @@ impl FilterBlocks { accounts, entries, }), - ) - }))) + )); + } + messages } } @@ -1270,7 +1220,7 @@ mod tests { let message_transaction = create_message_transaction(&keypair_b, vec![account_key_b, account_key_a]); let message = Message::Transaction(message_transaction); - let updates = filter.get_filters(&message, None).collect::>(); + let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); assert!(matches!( @@ -1323,7 +1273,7 @@ mod tests { let message_transaction = create_message_transaction(&keypair_b, vec![account_key_b, account_key_a]); let message = Message::Transaction(message_transaction); - let updates = filter.get_filters(&message, None).collect::>(); + let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); assert!(matches!( @@ -1428,7 +1378,7 @@ mod tests { vec![account_key_x, account_key_y, account_key_z], ); let message = Message::Transaction(message_transaction); - let updates = filter.get_filters(&message, None).collect::>(); + let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); assert!(matches!( From 69cceb82106b159dd7cbfde0f7699aaae5d3095b Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 10:03:10 +0200 Subject: [PATCH 10/50] use smallvec for filter names --- Cargo.lock | 1 + yellowstone-grpc-geyser-messages/Cargo.toml | 1 + .../src/filter.rs | 9 ++-- yellowstone-grpc-geyser/src/filters.rs | 48 ++++++++++++------- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7536fe50..cd8d6e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4526,6 +4526,7 @@ dependencies = [ "bincode", "bytes", "prost", + "smallvec", "solana-sdk", "solana-transaction-status", "thiserror", diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml index 898eafae..0e747afa 100644 --- a/yellowstone-grpc-geyser-messages/Cargo.toml +++ b/yellowstone-grpc-geyser-messages/Cargo.toml @@ -17,6 +17,7 @@ bytes = { workspace = true } prost = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } +smallvec = { workspace = true } thiserror = { workspace = true } tonic = { workspace = true } diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-geyser-messages/src/filter.rs index 5b0fce7e..75bef9d7 100644 --- a/yellowstone-grpc-geyser-messages/src/filter.rs +++ b/yellowstone-grpc-geyser-messages/src/filter.rs @@ -5,6 +5,7 @@ use { }, bytes::buf::BufMut, prost::encoding::{encode_key, encode_varint, message, WireType}, + smallvec::SmallVec, std::{ borrow::Borrow, collections::HashSet, @@ -104,12 +105,12 @@ impl FilterNames { #[derive(Debug)] pub struct Message { - pub filters: Vec, // 1 - pub message: MessageWeak, // 2, 3, 4, 10, 5, 6, 9, 7, 8 + pub filters: MessageFilters, // 1 + pub message: MessageWeak, // 2, 3, 4, 10, 5, 6, 9, 7, 8 } impl Message { - pub fn new(filters: Vec, message: MessageWeak) -> Self { + pub fn new(filters: MessageFilters, message: MessageWeak) -> Self { Self { filters, message } } @@ -123,6 +124,8 @@ impl Message { } } +pub type MessageFilters = SmallVec<[FilterName; 4]>; + #[derive(Debug)] pub enum MessageWeak { Account, // 2 diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index ecacb89a..7ca3a1a5 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -17,7 +17,8 @@ use { yellowstone_grpc_geyser_messages::{ filter::{ FilterName, FilterNames, Message as FilteredMessage, - MessageWeak as FilteredMessageWeak, MessageWeakBlock as FilteredMessageWeakBlock, + MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + MessageWeakBlock as FilteredMessageWeakBlock, }, geyser::{ CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, @@ -53,7 +54,9 @@ macro_rules! filtered_messages_once_ref { ($filters:ident, $message:expr) => {{ let mut messages = FilteredMessages::new(); if !$filters.is_empty() { - messages.push(FilteredMessage::new($filters.to_vec(), $message)); + let mut message_filters = FilteredMessageFilters::new(); + message_filters.clone_from_slice($filters); + messages.push(FilteredMessage::new(message_filters, $message)); } messages }}; @@ -210,14 +213,14 @@ impl Filter { pub fn get_pong_msg(&self) -> Option { self.ping.map(|id| FilteredMessage { - filters: vec![], + filters: FilteredMessageFilters::new(), message: FilteredMessageWeak::pong(id), }) } - pub const fn create_ping_message() -> FilteredMessage { + pub fn create_ping_message() -> FilteredMessage { FilteredMessage { - filters: vec![], + filters: FilteredMessageFilters::new(), message: FilteredMessageWeak::Ping, } } @@ -505,7 +508,7 @@ impl<'a> FilterAccountsMatch<'a> { } } - pub fn get_filters(&self) -> Vec { + pub fn get_filters(&self) -> FilteredMessageFilters { self.filter .filters .iter() @@ -588,7 +591,7 @@ impl FilterSlots { None } }) - .collect::>(); + .collect::(); filtered_messages_once_owned!(filters, FilteredMessageWeak::slot(*message)) } } @@ -755,7 +758,7 @@ impl FilterTransactions { Some(name.clone()) }) - .collect::>(); + .collect::(); filtered_messages_once_owned!( filters, @@ -915,8 +918,10 @@ impl FilterBlocks { vec![] }; + let mut message_filters = FilteredMessageFilters::new(); + message_filters.push(filter.clone()); messages.push(FilteredMessage::new( - vec![filter.clone()], + message_filters, FilteredMessageWeak::block(FilteredMessageWeakBlock { meta: Arc::clone(&message.meta), transactions, @@ -1004,7 +1009,9 @@ mod tests { solana_transaction_status::TransactionStatusMeta, std::{collections::HashMap, sync::Arc, time::Duration}, yellowstone_grpc_geyser_messages::{ - filter::MessageWeak as FilteredMessageWeak, + filter::{ + MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + }, geyser::{Message, MessageTransaction, MessageTransactionInfo}, }, yellowstone_grpc_proto::geyser::{ @@ -1222,12 +1229,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); + assert_eq!( + updates[0].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[0].message, FilteredMessageWeak::Transaction )); - assert_eq!(updates[1].filters, Vec::::new()); + assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, FilteredMessageWeak::TransactionStatus @@ -1275,12 +1285,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); + assert_eq!( + updates[0].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[0].message, FilteredMessageWeak::Transaction )); - assert_eq!(updates[1].filters, Vec::::new()); + assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, FilteredMessageWeak::TransactionStatus @@ -1380,12 +1393,15 @@ mod tests { let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); assert_eq!(updates.len(), 2); - assert_eq!(updates[0].filters, vec![FilterName::new("serum")]); + assert_eq!( + updates[0].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[0].message, FilteredMessageWeak::Transaction )); - assert_eq!(updates[1].filters, Vec::::new()); + assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, FilteredMessageWeak::TransactionStatus From 6c13188d703580d6eb678e765b751562cb7d4965 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 10:54:11 +0200 Subject: [PATCH 11/50] rm messages crate --- Cargo.lock | 21 +--- Cargo.toml | 2 - yellowstone-grpc-geyser-messages/Cargo.toml | 25 ----- yellowstone-grpc-geyser/Cargo.toml | 1 - yellowstone-grpc-geyser/src/filters.rs | 57 +++++----- yellowstone-grpc-geyser/src/grpc.rs | 16 +-- yellowstone-grpc-geyser/src/metrics.rs | 2 +- yellowstone-grpc-geyser/src/plugin.rs | 2 +- yellowstone-grpc-proto/Cargo.toml | 11 +- yellowstone-grpc-proto/build.rs | 8 +- yellowstone-grpc-proto/src/lib.rs | 103 +----------------- yellowstone-grpc-proto/src/weak/codec.rs | 74 +++++++++++++ .../src/weak}/filter.rs | 2 +- .../src/weak}/geyser.rs | 22 ++++ .../src/weak/mod.rs | 1 + 15 files changed, 157 insertions(+), 190 deletions(-) delete mode 100644 yellowstone-grpc-geyser-messages/Cargo.toml create mode 100644 yellowstone-grpc-proto/src/weak/codec.rs rename {yellowstone-grpc-geyser-messages/src => yellowstone-grpc-proto/src/weak}/filter.rs (99%) rename {yellowstone-grpc-geyser-messages/src => yellowstone-grpc-proto/src/weak}/geyser.rs (89%) rename yellowstone-grpc-geyser-messages/src/lib.rs => yellowstone-grpc-proto/src/weak/mod.rs (68%) diff --git a/Cargo.lock b/Cargo.lock index cd8d6e2b..4f234802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4514,39 +4514,26 @@ dependencies = [ "tonic", "tonic-health", "vergen", - "yellowstone-grpc-geyser-messages", "yellowstone-grpc-proto", ] -[[package]] -name = "yellowstone-grpc-geyser-messages" -version = "2.0.0" -dependencies = [ - "agave-geyser-plugin-interface", - "bincode", - "bytes", - "prost", - "smallvec", - "solana-sdk", - "solana-transaction-status", - "thiserror", - "tonic", -] - [[package]] name = "yellowstone-grpc-proto" version = "2.0.0" dependencies = [ + "agave-geyser-plugin-interface", "anyhow", "bincode", + "bytes", "prost", "protobuf-src", + "smallvec", "solana-account-decoder", "solana-sdk", "solana-transaction-status", + "thiserror", "tonic", "tonic-build", - "yellowstone-grpc-geyser-messages", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2317b49c..4fbe7ad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "examples/rust", # 2.0.0 "yellowstone-grpc-client", # 2.0.0 "yellowstone-grpc-geyser", # 2.0.0 - "yellowstone-grpc-geyser-messages", # 2.0.0 "yellowstone-grpc-proto", # 2.0.0 ] @@ -63,7 +62,6 @@ tonic-build = "0.12.1" tonic-health = "0.12.1" vergen = "9.0.0" yellowstone-grpc-client = { path = "yellowstone-grpc-client", version = "2.0.0" } -yellowstone-grpc-geyser-messages = { path = "yellowstone-grpc-geyser-messages", version = "2.0.0" } yellowstone-grpc-proto = { path = "yellowstone-grpc-proto", version = "2.0.0", default-features = false } [workspace.lints.clippy] diff --git a/yellowstone-grpc-geyser-messages/Cargo.toml b/yellowstone-grpc-geyser-messages/Cargo.toml deleted file mode 100644 index 0e747afa..00000000 --- a/yellowstone-grpc-geyser-messages/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "yellowstone-grpc-geyser-messages" -version = "2.0.0" -authors = { workspace = true } -edition = { workspace = true } -description = "Yellowstone gRPC Geyser Plugin Messages" -homepage = { workspace = true } -repository = { workspace = true } -license = { workspace = true } -keywords = { workspace = true } -publish = { workspace = true } - -[dependencies] -agave-geyser-plugin-interface = { workspace = true } -bincode = { workspace = true } -bytes = { workspace = true } -prost = { workspace = true } -solana-sdk = { workspace = true } -solana-transaction-status = { workspace = true } -smallvec = { workspace = true } -thiserror = { workspace = true } -tonic = { workspace = true } - -[lints] -workspace = true diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 73567794..0abf2868 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -45,7 +45,6 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs"] } tokio-stream = { workspace = true } tonic = { workspace = true, features = ["gzip", "zstd", "tls", "tls-roots"] } tonic-health = { workspace = true } -yellowstone-grpc-geyser-messages = { workspace = true } yellowstone-grpc-proto = { workspace = true, features = ["convert", "geyser_weak"] } [build-dependencies] diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 7ca3a1a5..a2a5f9e1 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -14,28 +14,30 @@ use { str::FromStr, sync::Arc, }, - yellowstone_grpc_geyser_messages::{ - filter::{ - FilterName, FilterNames, Message as FilteredMessage, - MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, - MessageWeakBlock as FilteredMessageWeakBlock, + yellowstone_grpc_proto::{ + geyser_weak::{ + filter::{ + FilterName, FilterNames, Message as FilteredMessage, + MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + MessageWeakBlock as FilteredMessageWeakBlock, + }, + geyser::{ + CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, + MessageEntry, MessageSlot, MessageTransaction, + }, }, - geyser::{ - CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, - MessageSlot, MessageTransaction, + prelude::{ + subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, + subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, + subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, + CommitmentLevel as CommitmentLevelProto, SubscribeRequest, + SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, + SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, + SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, + SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, + SubscribeRequestFilterTransactions, }, }, - yellowstone_grpc_proto::prelude::{ - subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, - subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, - subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, - CommitmentLevel as CommitmentLevelProto, SubscribeRequest, - SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, - SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, - SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, - SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, - SubscribeRequestFilterTransactions, - }, }; pub type FilteredMessages = SmallVec<[FilteredMessage; 8]>; @@ -1008,14 +1010,17 @@ mod tests { }, solana_transaction_status::TransactionStatusMeta, std::{collections::HashMap, sync::Arc, time::Duration}, - yellowstone_grpc_geyser_messages::{ - filter::{ - MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + yellowstone_grpc_proto::{ + geyser::{ + SubscribeRequest, SubscribeRequestFilterAccounts, + SubscribeRequestFilterTransactions, + }, + geyser_weak::{ + filter::{ + MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + }, + geyser::{Message, MessageTransaction, MessageTransactionInfo}, }, - geyser::{Message, MessageTransaction, MessageTransactionInfo}, - }, - yellowstone_grpc_proto::geyser::{ - SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterTransactions, }, }; diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 6207f5e3..da3e240d 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -35,15 +35,15 @@ use { Request, Response, Result as TonicResult, Status, Streaming, }, tonic_health::server::health_reporter, - yellowstone_grpc_geyser_messages::{ - filter::{FilterNames, Message as FilteredMessage}, - geyser::{ - CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, - MessageTransactionInfo, - }, - }, yellowstone_grpc_proto::{ - geyser_weak::geyser_server::{Geyser, GeyserServer}, + geyser_weak::{ + filter::{FilterNames, Message as FilteredMessage}, + geyser::{ + CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, + MessageTransactionInfo, + }, + geyser_server::{Geyser, GeyserServer}, + }, prelude::{ CommitmentLevel as CommitmentLevelProto, GetBlockHeightRequest, GetBlockHeightResponse, GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse, diff --git a/yellowstone-grpc-geyser/src/metrics.rs b/yellowstone-grpc-geyser/src/metrics.rs index 3fc74b91..06433346 100644 --- a/yellowstone-grpc-geyser/src/metrics.rs +++ b/yellowstone-grpc-geyser/src/metrics.rs @@ -24,7 +24,7 @@ use { sync::{mpsc, oneshot, Notify}, task::JoinHandle, }, - yellowstone_grpc_geyser_messages::geyser::CommitmentLevel, + yellowstone_grpc_proto::geyser_weak::geyser::CommitmentLevel, }; lazy_static::lazy_static! { diff --git a/yellowstone-grpc-geyser/src/plugin.rs b/yellowstone-grpc-geyser/src/plugin.rs index 8bc1e987..afeff47d 100644 --- a/yellowstone-grpc-geyser/src/plugin.rs +++ b/yellowstone-grpc-geyser/src/plugin.rs @@ -21,7 +21,7 @@ use { runtime::{Builder, Runtime}, sync::{mpsc, Notify}, }, - yellowstone_grpc_geyser_messages::geyser::Message, + yellowstone_grpc_proto::geyser_weak::geyser::Message, }; #[derive(Debug)] diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index 8a904694..ff732218 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -11,13 +11,16 @@ keywords = { workspace = true } publish = true [dependencies] -bincode = { workspace = true } +agave-geyser-plugin-interface = { workspace = true, optional = true } +bincode = { workspace = true, optional = true } +bytes = { workspace = true, optional = true } prost = { workspace = true } +smallvec = { workspace = true, optional = true } solana-account-decoder = { workspace = true, optional = true } solana-sdk = { workspace = true, optional = true } solana-transaction-status = { workspace = true, optional = true } +thiserror = { workspace = true, optional = true } tonic = { workspace = true } -yellowstone-grpc-geyser-messages = { workspace = true, optional = true } [build-dependencies] anyhow = { workspace = true } @@ -25,8 +28,8 @@ protobuf-src = { workspace = true } tonic-build = { workspace = true } [features] -convert = ["dep:solana-account-decoder", "dep:solana-sdk", "dep:solana-transaction-status"] -geyser_weak = ["dep:yellowstone-grpc-geyser-messages"] +convert = ["dep:bincode", "dep:solana-account-decoder", "dep:solana-sdk", "dep:solana-transaction-status"] +geyser_weak = ["dep:agave-geyser-plugin-interface", "dep:bytes", "dep:smallvec", "dep:solana-sdk", "dep:solana-transaction-status", "dep:thiserror"] tonic-compression = ["tonic/gzip", "tonic/zstd"] default = ["convert", "tonic-compression"] diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index aeae5409..9a0eb958 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -17,8 +17,8 @@ fn main() -> anyhow::Result<()> { .input_type("crate::geyser::SubscribeRequest") // .output_type("crate::geyser::SubscribeUpdate") // .codec_path("tonic::codec::ProstCodec") - .output_type("crate::geyser_weak::FilteredMessage") - .codec_path("crate::geyser_weak::SubscribeCodec") + .output_type("crate::geyser_weak::filter::Message") + .codec_path("crate::geyser_weak::codec::SubscribeCodec") .client_streaming() .server_streaming() .build(), @@ -87,8 +87,8 @@ fn main() -> anyhow::Result<()> { location.push("geyser.Geyser.rs"); let geyser_rs = std::fs::read_to_string(location.clone())?; let geyser_rs = geyser_rs.replace( - "let codec = crate::geyser_weak::SubscribeCodec::default();", - "let codec = crate::geyser_weak::SubscribeCodec::::default();", + "let codec = crate::geyser_weak::codec::SubscribeCodec::default();", + "let codec = crate::geyser_weak::codec::SubscribeCodec::::default();", ); std::fs::write(location, geyser_rs)?; diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 72f0d7c5..3a3884a1 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -5,34 +5,10 @@ pub mod geyser { #![allow(clippy::missing_const_for_fn)] tonic::include_proto!("geyser"); - - #[cfg(feature = "geyser_weak")] - use yellowstone_grpc_geyser_messages::geyser::CommitmentLevel as CommitmentLevelMessages; - - #[cfg(feature = "geyser_weak")] - impl PartialEq for CommitmentLevelMessages { - fn eq(&self, other: &CommitmentLevel) -> bool { - matches!( - (self, other), - (Self::Processed, CommitmentLevel::Processed) - | (Self::Confirmed, CommitmentLevel::Confirmed) - | (Self::Finalized, CommitmentLevel::Finalized) - ) - } - } - - #[cfg(feature = "geyser_weak")] - impl From for CommitmentLevelMessages { - fn from(status: CommitmentLevel) -> Self { - match status { - CommitmentLevel::Processed => Self::Processed, - CommitmentLevel::Confirmed => Self::Confirmed, - CommitmentLevel::Finalized => Self::Finalized, - } - } - } } +#[cfg(feature = "geyser_weak")] +mod weak; #[cfg(feature = "geyser_weak")] pub mod geyser_weak { #![allow(clippy::clone_on_ref_ptr)] @@ -40,80 +16,7 @@ pub mod geyser_weak { tonic::include_proto!("geyser.Geyser"); - use { - prost::Message, - std::marker::PhantomData, - tonic::{ - codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, - Status, - }, - yellowstone_grpc_geyser_messages::filter::Message as FilteredMessage, - }; - - pub struct SubscribeCodec { - _pd: PhantomData<(T, U)>, - } - - impl Default for SubscribeCodec { - fn default() -> Self { - Self { _pd: PhantomData } - } - } - - impl Codec for SubscribeCodec - where - T: Send + 'static, - U: Message + Default + Send + 'static, - { - type Encode = FilteredMessage; - type Decode = U; - - type Encoder = SubscribeEncoder; - type Decoder = ProstDecoder; - - fn encoder(&mut self) -> Self::Encoder { - SubscribeEncoder(PhantomData) - } - - fn decoder(&mut self) -> Self::Decoder { - ProstDecoder(PhantomData) - } - } - - #[derive(Debug)] - pub struct SubscribeEncoder(PhantomData); - - impl Encoder for SubscribeEncoder { - type Item = FilteredMessage; - type Error = Status; - - fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { - item.encode(buf) - } - } - - /// A [`Decoder`] that knows how to decode `U`. - #[derive(Debug)] - pub struct ProstDecoder(PhantomData); - - impl Decoder for ProstDecoder { - type Item = U; - type Error = Status; - - fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { - let item = Message::decode(buf) - .map(Option::Some) - .map_err(from_decode_error)?; - - Ok(item) - } - } - - fn from_decode_error(error: prost::DecodeError) -> Status { - // Map Protobuf parse errors to an INTERNAL status code, as per - // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - Status::internal(error.to_string()) - } + pub use super::weak::*; } pub mod solana { diff --git a/yellowstone-grpc-proto/src/weak/codec.rs b/yellowstone-grpc-proto/src/weak/codec.rs new file mode 100644 index 00000000..6ae592a2 --- /dev/null +++ b/yellowstone-grpc-proto/src/weak/codec.rs @@ -0,0 +1,74 @@ +use { + super::filter::Message as FilteredMessage, + prost::Message, + std::marker::PhantomData, + tonic::{ + codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, + Status, + }, +}; + +pub struct SubscribeCodec { + _pd: PhantomData<(T, U)>, +} + +impl Default for SubscribeCodec { + fn default() -> Self { + Self { _pd: PhantomData } + } +} + +impl Codec for SubscribeCodec +where + T: Send + 'static, + U: Message + Default + Send + 'static, +{ + type Encode = FilteredMessage; + type Decode = U; + + type Encoder = SubscribeEncoder; + type Decoder = ProstDecoder; + + fn encoder(&mut self) -> Self::Encoder { + SubscribeEncoder(PhantomData) + } + + fn decoder(&mut self) -> Self::Decoder { + ProstDecoder(PhantomData) + } +} + +#[derive(Debug)] +pub struct SubscribeEncoder(PhantomData); + +impl Encoder for SubscribeEncoder { + type Item = FilteredMessage; + type Error = Status; + + fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { + item.encode(buf) + } +} + +/// A [`Decoder`] that knows how to decode `U`. +#[derive(Debug)] +pub struct ProstDecoder(PhantomData); + +impl Decoder for ProstDecoder { + type Item = U; + type Error = Status; + + fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { + let item = Message::decode(buf) + .map(Option::Some) + .map_err(from_decode_error)?; + + Ok(item) + } +} + +fn from_decode_error(error: prost::DecodeError) -> Status { + // Map Protobuf parse errors to an INTERNAL status code, as per + // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + Status::internal(error.to_string()) +} diff --git a/yellowstone-grpc-geyser-messages/src/filter.rs b/yellowstone-grpc-proto/src/weak/filter.rs similarity index 99% rename from yellowstone-grpc-geyser-messages/src/filter.rs rename to yellowstone-grpc-proto/src/weak/filter.rs index 75bef9d7..33a41799 100644 --- a/yellowstone-grpc-geyser-messages/src/filter.rs +++ b/yellowstone-grpc-proto/src/weak/filter.rs @@ -1,5 +1,5 @@ use { - crate::geyser::{ + super::geyser::{ MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, MessageTransactionInfo, }, diff --git a/yellowstone-grpc-geyser-messages/src/geyser.rs b/yellowstone-grpc-proto/src/weak/geyser.rs similarity index 89% rename from yellowstone-grpc-geyser-messages/src/geyser.rs rename to yellowstone-grpc-proto/src/weak/geyser.rs index 1dca4bec..48b7e587 100644 --- a/yellowstone-grpc-geyser-messages/src/geyser.rs +++ b/yellowstone-grpc-proto/src/weak/geyser.rs @@ -1,4 +1,5 @@ use { + crate::geyser::CommitmentLevel as CommitmentLevelProto, agave_geyser_plugin_interface::geyser_plugin_interface::{ ReplicaAccountInfoV3, ReplicaBlockInfoV4, ReplicaEntryInfoV2, ReplicaTransactionInfoV2, SlotStatus, @@ -28,6 +29,27 @@ impl From for CommitmentLevel { } } +impl PartialEq for CommitmentLevel { + fn eq(&self, other: &CommitmentLevelProto) -> bool { + matches!( + (self, other), + (Self::Processed, CommitmentLevelProto::Processed) + | (Self::Confirmed, CommitmentLevelProto::Confirmed) + | (Self::Finalized, CommitmentLevelProto::Finalized) + ) + } +} + +impl From for CommitmentLevel { + fn from(status: CommitmentLevelProto) -> Self { + match status { + CommitmentLevelProto::Processed => Self::Processed, + CommitmentLevelProto::Confirmed => Self::Confirmed, + CommitmentLevelProto::Finalized => Self::Finalized, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct MessageSlot { pub slot: u64, diff --git a/yellowstone-grpc-geyser-messages/src/lib.rs b/yellowstone-grpc-proto/src/weak/mod.rs similarity index 68% rename from yellowstone-grpc-geyser-messages/src/lib.rs rename to yellowstone-grpc-proto/src/weak/mod.rs index 1e231136..af1c642a 100644 --- a/yellowstone-grpc-geyser-messages/src/lib.rs +++ b/yellowstone-grpc-proto/src/weak/mod.rs @@ -1,2 +1,3 @@ +pub mod codec; pub mod filter; pub mod geyser; From 4cfe956037550a122fa60aeb39385104e5b168bb Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 18:23:02 +0200 Subject: [PATCH 12/50] drop lock --- yellowstone-grpc-geyser/src/grpc.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index da3e240d..ea1f4e50 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -1019,7 +1019,9 @@ impl Geyser for GrpcService { let mut filter_names = filter_names.lock().await; filter_names.try_clean(); - if let Err(error) = match Filter::new(&request, &config_filters, &mut filter_names) { + let maybe_filter = Filter::new(&request, &config_filters, &mut filter_names); + drop(filter_names); + if let Err(error) = match maybe_filter { Ok(filter) => match incoming_client_tx.send(Some(filter)) { Ok(()) => Ok(()), Err(error) => Err(error.to_string()), From a78c3261220b95fe155520f7e0900cb838e8cca4 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 6 Nov 2024 22:34:52 +0200 Subject: [PATCH 13/50] simple ping/pong test --- yellowstone-grpc-proto/src/weak/filter.rs | 86 +++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/yellowstone-grpc-proto/src/weak/filter.rs b/yellowstone-grpc-proto/src/weak/filter.rs index 33a41799..6ba2b53a 100644 --- a/yellowstone-grpc-proto/src/weak/filter.rs +++ b/yellowstone-grpc-proto/src/weak/filter.rs @@ -3,6 +3,9 @@ use { MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, MessageTransactionInfo, }, + crate::geyser::{ + subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdatePing, SubscribeUpdatePong, + }, bytes::buf::BufMut, prost::encoding::{encode_key, encode_varint, message, WireType}, smallvec::SmallVec, @@ -114,7 +117,7 @@ impl Message { Self { filters, message } } - pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { + pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), Status> { for name in self.filters.iter().map(|filter| filter.as_ref()) { encode_key(1, WireType::LengthDelimited, buf); encode_varint(name.len() as u64, buf); @@ -172,15 +175,18 @@ impl MessageWeak { Self::Entry(MessageWeakEntry(Arc::clone(message))) } - pub fn encode(self, buf: &mut EncodeBuf<'_>) -> Result<(), Status> { + pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), Status> { match self { MessageWeak::Account => todo!(), MessageWeak::Slot => todo!(), MessageWeak::Transaction => todo!(), MessageWeak::TransactionStatus => todo!(), MessageWeak::Block => todo!(), - MessageWeak::Ping => encode_key(6, WireType::LengthDelimited, buf), - MessageWeak::Pong(msg) => message::encode(9, &msg, buf), + MessageWeak::Ping => { + encode_key(6, WireType::LengthDelimited, buf); + encode_varint(0, buf); + } + MessageWeak::Pong(msg) => message::encode(9, msg, buf), MessageWeak::BlockMeta => todo!(), MessageWeak::Entry(msg) => todo!(), } @@ -208,6 +214,78 @@ pub struct MessageWeakPong { #[derive(Debug)] pub struct MessageWeakEntry(Arc); +impl From<&Message> for SubscribeUpdate { + fn from(message: &Message) -> Self { + SubscribeUpdate { + filters: message + .filters + .iter() + .map(|f| f.as_ref().to_owned()) + .collect(), + update_oneof: Some((&message.message).into()), + } + } +} + +impl From<&MessageWeak> for UpdateOneof { + fn from(message: &MessageWeak) -> Self { + match message { + MessageWeak::Account => todo!(), + MessageWeak::Slot => todo!(), + MessageWeak::Transaction => todo!(), + MessageWeak::TransactionStatus => todo!(), + MessageWeak::Block => todo!(), + MessageWeak::Ping => Self::Ping(SubscribeUpdatePing {}), + MessageWeak::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), + MessageWeak::BlockMeta => todo!(), + MessageWeak::Entry(msg) => todo!(), + } + } +} + +#[cfg(test)] +mod tests { + use { + super::{FilterName, Message, MessageFilters, MessageWeak}, + crate::geyser::{subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdatePing}, + bytes::BytesMut, + prost::Message as _, + }; + + fn create_message_filters(names: &[&str]) -> MessageFilters { + let mut filters = MessageFilters::new(); + for name in names { + filters.push(FilterName::new(*name)); + } + filters + } + + fn encode_decode_cmp(filters: &[&str], message: MessageWeak) { + let msg = Message { + filters: create_message_filters(filters), + message, + }; + + // println!("{:?}", SubscribeUpdate::from(&msg)); + let mut bytes = BytesMut::new(); + msg.encode(&mut bytes).expect("failed to encode"); + let update = SubscribeUpdate::decode(bytes).expect("failed to decode"); + // println!("{update:?}"); + assert_eq!(update, SubscribeUpdate::from(&msg)); + } + + #[test] + fn test_message_ping() { + encode_decode_cmp(&["123"], MessageWeak::Ping) + } + + #[test] + fn test_message_pong() { + encode_decode_cmp(&["123"], MessageWeak::pong(0)); + encode_decode_cmp(&["123"], MessageWeak::pong(42)); + } +} + // #[derive(Debug, Clone)] // pub enum FilteredMessage2<'a> { // Slot(&'a MessageSlot), From 1f81f32a83aa45c73a95db07a271ded35ab42d2c Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 00:13:19 +0200 Subject: [PATCH 14/50] impl Message and rm custom codec --- yellowstone-grpc-proto/build.rs | 4 +- yellowstone-grpc-proto/src/weak/codec.rs | 74 ------ yellowstone-grpc-proto/src/weak/filter.rs | 285 +++++++++++++++++----- yellowstone-grpc-proto/src/weak/geyser.rs | 4 +- yellowstone-grpc-proto/src/weak/mod.rs | 1 - 5 files changed, 232 insertions(+), 136 deletions(-) delete mode 100644 yellowstone-grpc-proto/src/weak/codec.rs diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index 9a0eb958..e0c8933d 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -16,9 +16,9 @@ fn main() -> anyhow::Result<()> { .route_name("Subscribe") .input_type("crate::geyser::SubscribeRequest") // .output_type("crate::geyser::SubscribeUpdate") - // .codec_path("tonic::codec::ProstCodec") + .codec_path("tonic::codec::ProstCodec") .output_type("crate::geyser_weak::filter::Message") - .codec_path("crate::geyser_weak::codec::SubscribeCodec") + // .codec_path("crate::geyser_weak::codec::SubscribeCodec") .client_streaming() .server_streaming() .build(), diff --git a/yellowstone-grpc-proto/src/weak/codec.rs b/yellowstone-grpc-proto/src/weak/codec.rs deleted file mode 100644 index 6ae592a2..00000000 --- a/yellowstone-grpc-proto/src/weak/codec.rs +++ /dev/null @@ -1,74 +0,0 @@ -use { - super::filter::Message as FilteredMessage, - prost::Message, - std::marker::PhantomData, - tonic::{ - codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, - Status, - }, -}; - -pub struct SubscribeCodec { - _pd: PhantomData<(T, U)>, -} - -impl Default for SubscribeCodec { - fn default() -> Self { - Self { _pd: PhantomData } - } -} - -impl Codec for SubscribeCodec -where - T: Send + 'static, - U: Message + Default + Send + 'static, -{ - type Encode = FilteredMessage; - type Decode = U; - - type Encoder = SubscribeEncoder; - type Decoder = ProstDecoder; - - fn encoder(&mut self) -> Self::Encoder { - SubscribeEncoder(PhantomData) - } - - fn decoder(&mut self) -> Self::Decoder { - ProstDecoder(PhantomData) - } -} - -#[derive(Debug)] -pub struct SubscribeEncoder(PhantomData); - -impl Encoder for SubscribeEncoder { - type Item = FilteredMessage; - type Error = Status; - - fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { - item.encode(buf) - } -} - -/// A [`Decoder`] that knows how to decode `U`. -#[derive(Debug)] -pub struct ProstDecoder(PhantomData); - -impl Decoder for ProstDecoder { - type Item = U; - type Error = Status; - - fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { - let item = Message::decode(buf) - .map(Option::Some) - .map_err(from_decode_error)?; - - Ok(item) - } -} - -fn from_decode_error(error: prost::DecodeError) -> Status { - // Map Protobuf parse errors to an INTERNAL status code, as per - // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - Status::internal(error.to_string()) -} diff --git a/yellowstone-grpc-proto/src/weak/filter.rs b/yellowstone-grpc-proto/src/weak/filter.rs index 6ba2b53a..fa18da61 100644 --- a/yellowstone-grpc-proto/src/weak/filter.rs +++ b/yellowstone-grpc-proto/src/weak/filter.rs @@ -4,10 +4,17 @@ use { MessageTransaction, MessageTransactionInfo, }, crate::geyser::{ - subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdatePing, SubscribeUpdatePong, + subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateEntry, SubscribeUpdatePing, + SubscribeUpdatePong, + }, + bytes::buf::{Buf, BufMut}, + prost::{ + encoding::{ + encode_key, encode_varint, encoded_len_varint, key_len, message, DecodeContext, + WireType, + }, + DecodeError, }, - bytes::buf::BufMut, - prost::encoding::{encode_key, encode_varint, message, WireType}, smallvec::SmallVec, std::{ borrow::Borrow, @@ -16,7 +23,6 @@ use { sync::Arc, time::{Duration, Instant}, }, - tonic::{codec::EncodeBuf, Status}, }; #[derive(Debug, thiserror::Error)] @@ -112,18 +118,59 @@ pub struct Message { pub message: MessageWeak, // 2, 3, 4, 10, 5, 6, 9, 7, 8 } -impl Message { - pub fn new(filters: MessageFilters, message: MessageWeak) -> Self { - Self { filters, message } +impl From<&Message> for SubscribeUpdate { + fn from(message: &Message) -> Self { + SubscribeUpdate { + filters: message + .filters + .iter() + .map(|f| f.as_ref().to_owned()) + .collect(), + update_oneof: Some((&message.message).into()), + } } +} - pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), Status> { +impl prost::Message for Message { + fn encode_raw(&self, buf: &mut impl BufMut) { for name in self.filters.iter().map(|filter| filter.as_ref()) { - encode_key(1, WireType::LengthDelimited, buf); + encode_key(1u32, WireType::LengthDelimited, buf); encode_varint(name.len() as u64, buf); buf.put_slice(name.as_bytes()); } - self.message.encode(buf) + self.message.encode_raw(buf) + } + + fn encoded_len(&self) -> usize { + key_len(1u32) * self.filters.len() + + self + .filters + .iter() + .map(|filter| { + encoded_len_varint(filter.as_ref().len() as u64) + filter.as_ref().len() + }) + .sum::() + + self.message.encoded_len() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +impl Message { + pub fn new(filters: MessageFilters, message: MessageWeak) -> Self { + Self { filters, message } } } @@ -142,6 +189,69 @@ pub enum MessageWeak { Entry(MessageWeakEntry), // 8 } +impl From<&MessageWeak> for UpdateOneof { + fn from(message: &MessageWeak) -> Self { + match message { + MessageWeak::Account => todo!(), + MessageWeak::Slot => todo!(), + MessageWeak::Transaction => todo!(), + MessageWeak::TransactionStatus => todo!(), + MessageWeak::Block => todo!(), + MessageWeak::Ping => Self::Ping(SubscribeUpdatePing {}), + MessageWeak::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), + MessageWeak::BlockMeta => todo!(), + MessageWeak::Entry(msg) => Self::Entry(msg.into()), + } + } +} + +impl prost::Message for MessageWeak { + fn encode_raw(&self, buf: &mut impl BufMut) { + match self { + MessageWeak::Account => todo!(), + MessageWeak::Slot => todo!(), + MessageWeak::Transaction => todo!(), + MessageWeak::TransactionStatus => todo!(), + MessageWeak::Block => todo!(), + MessageWeak::Ping => { + encode_key(6u32, WireType::LengthDelimited, buf); + encode_varint(0, buf); + } + MessageWeak::Pong(msg) => message::encode(9u32, msg, buf), + MessageWeak::BlockMeta => todo!(), + MessageWeak::Entry(msg) => message::encode(8u32, msg, buf), + } + } + + fn encoded_len(&self) -> usize { + match self { + MessageWeak::Account => todo!(), + MessageWeak::Slot => todo!(), + MessageWeak::Transaction => todo!(), + MessageWeak::TransactionStatus => todo!(), + MessageWeak::Block => todo!(), + MessageWeak::Ping => 0, + MessageWeak::Pong(msg) => message::encoded_len(9u32, msg), + MessageWeak::BlockMeta => todo!(), + MessageWeak::Entry(msg) => message::encoded_len(8u32, msg), + } + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + impl MessageWeak { pub fn account(message: &MessageAccount, accounts_data_slice: Vec>) -> Self { todo!() @@ -174,25 +284,6 @@ impl MessageWeak { pub fn entry(message: &Arc) -> Self { Self::Entry(MessageWeakEntry(Arc::clone(message))) } - - pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), Status> { - match self { - MessageWeak::Account => todo!(), - MessageWeak::Slot => todo!(), - MessageWeak::Transaction => todo!(), - MessageWeak::TransactionStatus => todo!(), - MessageWeak::Block => todo!(), - MessageWeak::Ping => { - encode_key(6, WireType::LengthDelimited, buf); - encode_varint(0, buf); - } - MessageWeak::Pong(msg) => message::encode(9, msg, buf), - MessageWeak::BlockMeta => todo!(), - MessageWeak::Entry(msg) => todo!(), - } - - Ok(()) - } } #[derive(Debug)] @@ -212,44 +303,109 @@ pub struct MessageWeakPong { } #[derive(Debug)] -pub struct MessageWeakEntry(Arc); +pub struct MessageWeakEntry(pub Arc); -impl From<&Message> for SubscribeUpdate { - fn from(message: &Message) -> Self { - SubscribeUpdate { - filters: message - .filters - .iter() - .map(|f| f.as_ref().to_owned()) - .collect(), - update_oneof: Some((&message.message).into()), +impl From<&MessageWeakEntry> for SubscribeUpdateEntry { + fn from(MessageWeakEntry(msg): &MessageWeakEntry) -> Self { + Self { + slot: msg.slot, + index: msg.index as u64, + num_hashes: msg.num_hashes, + hash: msg.hash.into(), + executed_transaction_count: msg.executed_transaction_count, + starting_transaction_index: msg.starting_transaction_index, } } } -impl From<&MessageWeak> for UpdateOneof { - fn from(message: &MessageWeak) -> Self { - match message { - MessageWeak::Account => todo!(), - MessageWeak::Slot => todo!(), - MessageWeak::Transaction => todo!(), - MessageWeak::TransactionStatus => todo!(), - MessageWeak::Block => todo!(), - MessageWeak::Ping => Self::Ping(SubscribeUpdatePing {}), - MessageWeak::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), - MessageWeak::BlockMeta => todo!(), - MessageWeak::Entry(msg) => todo!(), +impl prost::Message for MessageWeakEntry { + fn encode_raw(&self, buf: &mut impl BufMut) { + let msg = &self.0; + let index = msg.index as u64; + if msg.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); + } + if index != 0u64 { + ::prost::encoding::uint64::encode(2u32, &index, buf); + } + if msg.num_hashes != 0u64 { + ::prost::encoding::uint64::encode(3u32, &msg.num_hashes, buf); + } + if !msg.hash.is_empty() { + prost_bytes_encode_raw(4u32, &msg.hash, buf); + } + if msg.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encode(5u32, &msg.executed_transaction_count, buf); + } + if msg.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encode(6u32, &msg.starting_transaction_index, buf); } } + + fn encoded_len(&self) -> usize { + let msg = &self.0; + let index = msg.index as u64; + (if msg.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + } else { + 0 + }) + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &index) + } else { + 0 + } + if msg.num_hashes != 0u64 { + ::prost::encoding::uint64::encoded_len(3u32, &msg.num_hashes) + } else { + 0 + } + if !msg.hash.is_empty() { + prost_bytes_encoded_len(4u32, &msg.hash) + } else { + 0 + } + if msg.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &msg.executed_transaction_count) + } else { + 0 + } + if msg.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encoded_len(6u32, &msg.starting_transaction_index) + } else { + 0 + } + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +#[inline] +fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(value.len() as u64, buf); + buf.put(value); +} + +#[inline] +pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { + key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() } #[cfg(test)] mod tests { use { - super::{FilterName, Message, MessageFilters, MessageWeak}, - crate::geyser::{subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdatePing}, - bytes::BytesMut, + super::{FilterName, Message, MessageEntry, MessageFilters, MessageWeak}, + crate::geyser::SubscribeUpdate, prost::Message as _, + std::sync::Arc, }; fn create_message_filters(names: &[&str]) -> MessageFilters { @@ -265,11 +421,9 @@ mod tests { filters: create_message_filters(filters), message, }; - // println!("{:?}", SubscribeUpdate::from(&msg)); - let mut bytes = BytesMut::new(); - msg.encode(&mut bytes).expect("failed to encode"); - let update = SubscribeUpdate::decode(bytes).expect("failed to decode"); + let bytes = msg.encode_to_vec(); + let update = SubscribeUpdate::decode(bytes.as_slice()).expect("failed to decode"); // println!("{update:?}"); assert_eq!(update, SubscribeUpdate::from(&msg)); } @@ -284,6 +438,21 @@ mod tests { encode_decode_cmp(&["123"], MessageWeak::pong(0)); encode_decode_cmp(&["123"], MessageWeak::pong(42)); } + + #[test] + fn test_message_entry() { + encode_decode_cmp( + &["123"], + MessageWeak::entry(&Arc::new(MessageEntry { + slot: 299888121, + index: 42, + num_hashes: 128, + hash: [98; 32], + executed_transaction_count: 32, + starting_transaction_index: 1000, + })), + ); + } } // #[derive(Debug, Clone)] diff --git a/yellowstone-grpc-proto/src/weak/geyser.rs b/yellowstone-grpc-proto/src/weak/geyser.rs index 48b7e587..e3d71f75 100644 --- a/yellowstone-grpc-proto/src/weak/geyser.rs +++ b/yellowstone-grpc-proto/src/weak/geyser.rs @@ -151,7 +151,9 @@ impl From<&ReplicaEntryInfoV2<'_>> for MessageEntry { slot: entry.slot, index: entry.index, num_hashes: entry.num_hashes, - hash: entry.hash[0..32].try_into().expect("failed to create hash"), + hash: entry.hash[0..HASH_BYTES] + .try_into() + .expect("failed to create hash"), executed_transaction_count: entry.executed_transaction_count, starting_transaction_index: entry .starting_transaction_index diff --git a/yellowstone-grpc-proto/src/weak/mod.rs b/yellowstone-grpc-proto/src/weak/mod.rs index af1c642a..1e231136 100644 --- a/yellowstone-grpc-proto/src/weak/mod.rs +++ b/yellowstone-grpc-proto/src/weak/mod.rs @@ -1,3 +1,2 @@ -pub mod codec; pub mod filter; pub mod geyser; From ac2cc39180367c9b1fd3910451b4712cb516d757 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 09:37:11 +0200 Subject: [PATCH 15/50] block meta encoding draft --- yellowstone-grpc-proto/Cargo.toml | 10 +- yellowstone-grpc-proto/src/weak/filter.rs | 154 +++++++++++++++++++--- 2 files changed, 147 insertions(+), 17 deletions(-) diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index ff732218..91bb715a 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -29,7 +29,15 @@ tonic-build = { workspace = true } [features] convert = ["dep:bincode", "dep:solana-account-decoder", "dep:solana-sdk", "dep:solana-transaction-status"] -geyser_weak = ["dep:agave-geyser-plugin-interface", "dep:bytes", "dep:smallvec", "dep:solana-sdk", "dep:solana-transaction-status", "dep:thiserror"] +geyser_weak = [ + "convert", + "dep:agave-geyser-plugin-interface", + "dep:bytes", + "dep:smallvec", + "dep:solana-sdk", + "dep:solana-transaction-status", + "dep:thiserror" +] tonic-compression = ["tonic/gzip", "tonic/zstd"] default = ["convert", "tonic-compression"] diff --git a/yellowstone-grpc-proto/src/weak/filter.rs b/yellowstone-grpc-proto/src/weak/filter.rs index fa18da61..d55166b4 100644 --- a/yellowstone-grpc-proto/src/weak/filter.rs +++ b/yellowstone-grpc-proto/src/weak/filter.rs @@ -3,9 +3,12 @@ use { MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, MessageTransactionInfo, }, - crate::geyser::{ - subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateEntry, SubscribeUpdatePing, - SubscribeUpdatePong, + crate::{ + convert_to, + geyser::{ + subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateBlockMeta, + SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, + }, }, bytes::buf::{Buf, BufMut}, prost::{ @@ -178,15 +181,15 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; #[derive(Debug)] pub enum MessageWeak { - Account, // 2 - Slot, // 3 - Transaction, // 4 - TransactionStatus, // 10 - Block, // 5 - Ping, // 6 - Pong(MessageWeakPong), // 9 - BlockMeta, // 7 - Entry(MessageWeakEntry), // 8 + Account, // 2 + Slot, // 3 + Transaction, // 4 + TransactionStatus, // 10 + Block, // 5 + Ping, // 6 + Pong(MessageWeakPong), // 9 + BlockMeta(MessageWeakBlockMeta), // 7 + Entry(MessageWeakEntry), // 8 } impl From<&MessageWeak> for UpdateOneof { @@ -199,7 +202,7 @@ impl From<&MessageWeak> for UpdateOneof { MessageWeak::Block => todo!(), MessageWeak::Ping => Self::Ping(SubscribeUpdatePing {}), MessageWeak::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), - MessageWeak::BlockMeta => todo!(), + MessageWeak::BlockMeta(msg) => Self::BlockMeta(msg.into()), MessageWeak::Entry(msg) => Self::Entry(msg.into()), } } @@ -218,7 +221,7 @@ impl prost::Message for MessageWeak { encode_varint(0, buf); } MessageWeak::Pong(msg) => message::encode(9u32, msg, buf), - MessageWeak::BlockMeta => todo!(), + MessageWeak::BlockMeta(msg) => message::encode(7u32, msg, buf), MessageWeak::Entry(msg) => message::encode(8u32, msg, buf), } } @@ -232,7 +235,7 @@ impl prost::Message for MessageWeak { MessageWeak::Block => todo!(), MessageWeak::Ping => 0, MessageWeak::Pong(msg) => message::encoded_len(9u32, msg), - MessageWeak::BlockMeta => todo!(), + MessageWeak::BlockMeta(msg) => message::encoded_len(7u32, msg), MessageWeak::Entry(msg) => message::encoded_len(8u32, msg), } } @@ -278,7 +281,7 @@ impl MessageWeak { } pub fn block_meta(message: &Arc) -> Self { - todo!() + Self::BlockMeta(MessageWeakBlockMeta(Arc::clone(message))) } pub fn entry(message: &Arc) -> Self { @@ -302,6 +305,125 @@ pub struct MessageWeakPong { pub id: i32, } +#[derive(Debug)] +pub struct MessageWeakBlockMeta(pub Arc); + +impl From<&MessageWeakBlockMeta> for SubscribeUpdateBlockMeta { + fn from(MessageWeakBlockMeta(msg): &MessageWeakBlockMeta) -> Self { + Self { + slot: msg.slot, + blockhash: msg.blockhash.clone(), + rewards: Some(convert_to::create_rewards_obj( + msg.rewards.as_slice(), + msg.num_partitions, + )), + block_time: msg.block_time.map(convert_to::create_timestamp), + block_height: msg.block_height.map(convert_to::create_block_height), + parent_slot: msg.parent_slot, + parent_blockhash: msg.parent_blockhash.clone(), + executed_transaction_count: msg.executed_transaction_count, + entries_count: msg.entries_count, + } + } +} + +impl prost::Message for MessageWeakBlockMeta { + fn encode_raw(&self, buf: &mut impl BufMut) { + // let msg = &self.0; + // if self.slot != 0u64 { + // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); + // } + // if self.blockhash != "" { + // ::prost::encoding::string::encode(2u32, &self.blockhash, buf); + // } + // if let Some(ref msg) = self.rewards { + // ::prost::encoding::message::encode(3u32, msg, buf); + // } + // if let Some(ref msg) = self.block_time { + // ::prost::encoding::message::encode(4u32, msg, buf); + // } + // if let Some(ref msg) = self.block_height { + // ::prost::encoding::message::encode(5u32, msg, buf); + // } + // if self.parent_slot != 0u64 { + // ::prost::encoding::uint64::encode(6u32, &self.parent_slot, buf); + // } + // if self.parent_blockhash != "" { + // ::prost::encoding::string::encode(7u32, &self.parent_blockhash, buf); + // } + // if self.executed_transaction_count != 0u64 { + // ::prost::encoding::uint64::encode( + // 8u32, + // &self.executed_transaction_count, + // buf, + // ); + // } + // if self.entries_count != 0u64 { + // ::prost::encoding::uint64::encode(9u32, &self.entries_count, buf); + // } + todo!() + } + + fn encoded_len(&self) -> usize { + // let msg = &self.0; + // (if msg.slot != 0u64 { + // ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + // } else { + // 0 + // }) + if msg.blockhash != "" { + // ::prost::encoding::string::encoded_len(2u32, &msg.blockhash) + // } else { + // 0 + // } + self + // .rewards + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(3u32, msg)) + // + self + // .block_time + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) + // + self + // .block_height + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) + // + if msg.parent_slot != 0u64 { + // ::prost::encoding::uint64::encoded_len(6u32, &msg.parent_slot) + // } else { + // 0 + // } + // + if msg.parent_blockhash != "" { + // ::prost::encoding::string::encoded_len(7u32, &msg.parent_blockhash) + // } else { + // 0 + // } + // + if msg.executed_transaction_count != 0u64 { + // ::prost::encoding::uint64::encoded_len(8u32, &msg.executed_transaction_count) + // } else { + // 0 + // } + // + if msg.entries_count != 0u64 { + // ::prost::encoding::uint64::encoded_len(9u32, &msg.entries_count) + // } else { + // 0 + // } + todo!() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + #[derive(Debug)] pub struct MessageWeakEntry(pub Arc); From 33804048e5d8adb08e624703fe4a5070f5c19ba1 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 09:44:27 +0200 Subject: [PATCH 16/50] rename feature to plugin --- yellowstone-grpc-geyser/Cargo.toml | 2 +- yellowstone-grpc-geyser/src/filters.rs | 4 ++-- yellowstone-grpc-geyser/src/grpc.rs | 4 ++-- yellowstone-grpc-geyser/src/metrics.rs | 2 +- yellowstone-grpc-geyser/src/plugin.rs | 2 +- yellowstone-grpc-proto/Cargo.toml | 4 ++-- yellowstone-grpc-proto/build.rs | 4 ++-- yellowstone-grpc-proto/src/lib.rs | 15 +++------------ .../src/{weak => plugin}/filter.rs | 0 .../src/{weak => plugin}/geyser.rs | 0 yellowstone-grpc-proto/src/plugin/mod.rs | 8 ++++++++ yellowstone-grpc-proto/src/weak/mod.rs | 2 -- 12 files changed, 22 insertions(+), 25 deletions(-) rename yellowstone-grpc-proto/src/{weak => plugin}/filter.rs (100%) rename yellowstone-grpc-proto/src/{weak => plugin}/geyser.rs (100%) create mode 100644 yellowstone-grpc-proto/src/plugin/mod.rs delete mode 100644 yellowstone-grpc-proto/src/weak/mod.rs diff --git a/yellowstone-grpc-geyser/Cargo.toml b/yellowstone-grpc-geyser/Cargo.toml index 0abf2868..0a999bbd 100644 --- a/yellowstone-grpc-geyser/Cargo.toml +++ b/yellowstone-grpc-geyser/Cargo.toml @@ -45,7 +45,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs"] } tokio-stream = { workspace = true } tonic = { workspace = true, features = ["gzip", "zstd", "tls", "tls-roots"] } tonic-health = { workspace = true } -yellowstone-grpc-proto = { workspace = true, features = ["convert", "geyser_weak"] } +yellowstone-grpc-proto = { workspace = true, features = ["convert", "plugin"] } [build-dependencies] anyhow = { workspace = true } diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index a2a5f9e1..60eb91e1 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -15,7 +15,7 @@ use { sync::Arc, }, yellowstone_grpc_proto::{ - geyser_weak::{ + plugin::{ filter::{ FilterName, FilterNames, Message as FilteredMessage, MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, @@ -1015,7 +1015,7 @@ mod tests { SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterTransactions, }, - geyser_weak::{ + plugin::{ filter::{ MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, }, diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index ea1f4e50..6cf5abb2 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -36,13 +36,13 @@ use { }, tonic_health::server::health_reporter, yellowstone_grpc_proto::{ - geyser_weak::{ + plugin::{ filter::{FilterNames, Message as FilteredMessage}, geyser::{ CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransactionInfo, }, - geyser_server::{Geyser, GeyserServer}, + proto::geyser_server::{Geyser, GeyserServer}, }, prelude::{ CommitmentLevel as CommitmentLevelProto, GetBlockHeightRequest, GetBlockHeightResponse, diff --git a/yellowstone-grpc-geyser/src/metrics.rs b/yellowstone-grpc-geyser/src/metrics.rs index 06433346..01edda36 100644 --- a/yellowstone-grpc-geyser/src/metrics.rs +++ b/yellowstone-grpc-geyser/src/metrics.rs @@ -24,7 +24,7 @@ use { sync::{mpsc, oneshot, Notify}, task::JoinHandle, }, - yellowstone_grpc_proto::geyser_weak::geyser::CommitmentLevel, + yellowstone_grpc_proto::plugin::geyser::CommitmentLevel, }; lazy_static::lazy_static! { diff --git a/yellowstone-grpc-geyser/src/plugin.rs b/yellowstone-grpc-geyser/src/plugin.rs index afeff47d..51a9184d 100644 --- a/yellowstone-grpc-geyser/src/plugin.rs +++ b/yellowstone-grpc-geyser/src/plugin.rs @@ -21,7 +21,7 @@ use { runtime::{Builder, Runtime}, sync::{mpsc, Notify}, }, - yellowstone_grpc_proto::geyser_weak::geyser::Message, + yellowstone_grpc_proto::plugin::geyser::Message, }; #[derive(Debug)] diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index 91bb715a..b9d1721e 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -28,8 +28,9 @@ protobuf-src = { workspace = true } tonic-build = { workspace = true } [features] +default = ["convert", "tonic-compression"] convert = ["dep:bincode", "dep:solana-account-decoder", "dep:solana-sdk", "dep:solana-transaction-status"] -geyser_weak = [ +plugin = [ "convert", "dep:agave-geyser-plugin-interface", "dep:bytes", @@ -39,7 +40,6 @@ geyser_weak = [ "dep:thiserror" ] tonic-compression = ["tonic/gzip", "tonic/zstd"] -default = ["convert", "tonic-compression"] [lints] workspace = true diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index e0c8933d..7f98d28f 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -17,8 +17,8 @@ fn main() -> anyhow::Result<()> { .input_type("crate::geyser::SubscribeRequest") // .output_type("crate::geyser::SubscribeUpdate") .codec_path("tonic::codec::ProstCodec") - .output_type("crate::geyser_weak::filter::Message") - // .codec_path("crate::geyser_weak::codec::SubscribeCodec") + .output_type("crate::plugin::filter::Message") + // .codec_path("crate::plugin::codec::SubscribeCodec") .client_streaming() .server_streaming() .build(), diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 3a3884a1..4561bb37 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -7,18 +7,6 @@ pub mod geyser { tonic::include_proto!("geyser"); } -#[cfg(feature = "geyser_weak")] -mod weak; -#[cfg(feature = "geyser_weak")] -pub mod geyser_weak { - #![allow(clippy::clone_on_ref_ptr)] - #![allow(clippy::missing_const_for_fn)] - - tonic::include_proto!("geyser.Geyser"); - - pub use super::weak::*; -} - pub mod solana { #![allow(clippy::missing_const_for_fn)] @@ -35,6 +23,9 @@ pub mod prelude { pub use {prost, tonic}; +#[cfg(feature = "plugin")] +pub mod plugin; + #[cfg(feature = "convert")] pub mod convert_to { use { diff --git a/yellowstone-grpc-proto/src/weak/filter.rs b/yellowstone-grpc-proto/src/plugin/filter.rs similarity index 100% rename from yellowstone-grpc-proto/src/weak/filter.rs rename to yellowstone-grpc-proto/src/plugin/filter.rs diff --git a/yellowstone-grpc-proto/src/weak/geyser.rs b/yellowstone-grpc-proto/src/plugin/geyser.rs similarity index 100% rename from yellowstone-grpc-proto/src/weak/geyser.rs rename to yellowstone-grpc-proto/src/plugin/geyser.rs diff --git a/yellowstone-grpc-proto/src/plugin/mod.rs b/yellowstone-grpc-proto/src/plugin/mod.rs new file mode 100644 index 00000000..96179dcb --- /dev/null +++ b/yellowstone-grpc-proto/src/plugin/mod.rs @@ -0,0 +1,8 @@ +pub mod filter; +pub mod geyser; + +pub mod proto { + #![allow(clippy::clone_on_ref_ptr)] + #![allow(clippy::missing_const_for_fn)] + tonic::include_proto!("geyser.Geyser"); +} diff --git a/yellowstone-grpc-proto/src/weak/mod.rs b/yellowstone-grpc-proto/src/weak/mod.rs deleted file mode 100644 index 1e231136..00000000 --- a/yellowstone-grpc-proto/src/weak/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod filter; -pub mod geyser; From 21fe0d2d4fb1d7b605db4a1f19e0560da459b5a3 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 09:56:38 +0200 Subject: [PATCH 17/50] rename weak to ref --- yellowstone-grpc-geyser/src/filters.rs | 48 +- yellowstone-grpc-geyser/src/grpc.rs | 5 +- yellowstone-grpc-geyser/src/metrics.rs | 2 +- yellowstone-grpc-geyser/src/plugin.rs | 2 +- yellowstone-grpc-proto/build.rs | 20 +- yellowstone-grpc-proto/src/plugin/filter.rs | 642 +----------------- .../src/plugin/{geyser.rs => message.rs} | 0 .../src/plugin/message_ref.rs | 635 +++++++++++++++++ yellowstone-grpc-proto/src/plugin/mod.rs | 3 +- 9 files changed, 681 insertions(+), 676 deletions(-) rename yellowstone-grpc-proto/src/plugin/{geyser.rs => message.rs} (100%) create mode 100644 yellowstone-grpc-proto/src/plugin/message_ref.rs diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 60eb91e1..2a837e64 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -16,15 +16,15 @@ use { }, yellowstone_grpc_proto::{ plugin::{ - filter::{ - FilterName, FilterNames, Message as FilteredMessage, - MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, - MessageWeakBlock as FilteredMessageWeakBlock, - }, - geyser::{ + filter::{FilterName, FilterNames}, + message::{ CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, }, + message_ref::{ + Message as FilteredMessage, MessageFilters as FilteredMessageFilters, + MessageRef as FilteredMessageRef, MessageRefBlock as FilteredMessageRefBlock, + }, }, prelude::{ subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof, @@ -216,14 +216,14 @@ impl Filter { pub fn get_pong_msg(&self) -> Option { self.ping.map(|id| FilteredMessage { filters: FilteredMessageFilters::new(), - message: FilteredMessageWeak::pong(id), + message: FilteredMessageRef::pong(id), }) } pub fn create_ping_message() -> FilteredMessage { FilteredMessage { filters: FilteredMessageFilters::new(), - message: FilteredMessageWeak::Ping, + message: FilteredMessageRef::Ping, } } } @@ -318,7 +318,7 @@ impl FilterAccounts { let filters = filter.get_filters(); filtered_messages_once_owned!( filters, - FilteredMessageWeak::account(message, accounts_data_slice.to_vec()) + FilteredMessageRef::account(message, accounts_data_slice.to_vec()) ) } } @@ -594,7 +594,7 @@ impl FilterSlots { } }) .collect::(); - filtered_messages_once_owned!(filters, FilteredMessageWeak::slot(*message)) + filtered_messages_once_owned!(filters, FilteredMessageRef::slot(*message)) } } @@ -765,9 +765,9 @@ impl FilterTransactions { filtered_messages_once_owned!( filters, match self.filter_type { - FilterTransactionsType::Transaction => FilteredMessageWeak::transaction(message), + FilterTransactionsType::Transaction => FilteredMessageRef::transaction(message), FilterTransactionsType::TransactionStatus => { - FilteredMessageWeak::transaction_status(message) + FilteredMessageRef::transaction_status(message) } } ) @@ -797,7 +797,7 @@ impl FilterEntries { fn get_filters(&self, message: &Arc) -> FilteredMessages { let filters = self.filters.as_slice(); - filtered_messages_once_ref!(filters, FilteredMessageWeak::entry(message)) + filtered_messages_once_ref!(filters, FilteredMessageRef::entry(message)) } } @@ -924,7 +924,7 @@ impl FilterBlocks { message_filters.push(filter.clone()); messages.push(FilteredMessage::new( message_filters, - FilteredMessageWeak::block(FilteredMessageWeakBlock { + FilteredMessageRef::block(FilteredMessageRefBlock { meta: Arc::clone(&message.meta), transactions, updated_account_count: message.updated_account_count, @@ -961,7 +961,7 @@ impl FilterBlocksMeta { fn get_filters(&self, message: &Arc) -> FilteredMessages { let filters = self.filters.as_slice(); - filtered_messages_once_ref!(filters, FilteredMessageWeak::block_meta(message)) + filtered_messages_once_ref!(filters, FilteredMessageRef::block_meta(message)) } } @@ -1016,10 +1016,10 @@ mod tests { SubscribeRequestFilterTransactions, }, plugin::{ - filter::{ - MessageFilters as FilteredMessageFilters, MessageWeak as FilteredMessageWeak, + message::{Message, MessageTransaction, MessageTransactionInfo}, + message_ref::{ + MessageFilters as FilteredMessageFilters, MessageRef as FilteredMessageRef, }, - geyser::{Message, MessageTransaction, MessageTransactionInfo}, }, }, }; @@ -1240,12 +1240,12 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageWeak::Transaction + FilteredMessageRef::Transaction )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageWeak::TransactionStatus + FilteredMessageRef::TransactionStatus )); } @@ -1296,12 +1296,12 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageWeak::Transaction + FilteredMessageRef::Transaction )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageWeak::TransactionStatus + FilteredMessageRef::TransactionStatus )); } @@ -1404,12 +1404,12 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageWeak::Transaction + FilteredMessageRef::Transaction )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageWeak::TransactionStatus + FilteredMessageRef::TransactionStatus )); } diff --git a/yellowstone-grpc-geyser/src/grpc.rs b/yellowstone-grpc-geyser/src/grpc.rs index 6cf5abb2..fd9e15fb 100644 --- a/yellowstone-grpc-geyser/src/grpc.rs +++ b/yellowstone-grpc-geyser/src/grpc.rs @@ -37,11 +37,12 @@ use { tonic_health::server::health_reporter, yellowstone_grpc_proto::{ plugin::{ - filter::{FilterNames, Message as FilteredMessage}, - geyser::{ + filter::FilterNames, + message::{ CommitmentLevel, Message, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransactionInfo, }, + message_ref::Message as FilteredMessage, proto::geyser_server::{Geyser, GeyserServer}, }, prelude::{ diff --git a/yellowstone-grpc-geyser/src/metrics.rs b/yellowstone-grpc-geyser/src/metrics.rs index 01edda36..16f50164 100644 --- a/yellowstone-grpc-geyser/src/metrics.rs +++ b/yellowstone-grpc-geyser/src/metrics.rs @@ -24,7 +24,7 @@ use { sync::{mpsc, oneshot, Notify}, task::JoinHandle, }, - yellowstone_grpc_proto::plugin::geyser::CommitmentLevel, + yellowstone_grpc_proto::plugin::message::CommitmentLevel, }; lazy_static::lazy_static! { diff --git a/yellowstone-grpc-geyser/src/plugin.rs b/yellowstone-grpc-geyser/src/plugin.rs index 51a9184d..6927ec7d 100644 --- a/yellowstone-grpc-geyser/src/plugin.rs +++ b/yellowstone-grpc-geyser/src/plugin.rs @@ -21,7 +21,7 @@ use { runtime::{Builder, Runtime}, sync::{mpsc, Notify}, }, - yellowstone_grpc_proto::plugin::geyser::Message, + yellowstone_grpc_proto::plugin::message::Message, }; #[derive(Debug)] diff --git a/yellowstone-grpc-proto/build.rs b/yellowstone-grpc-proto/build.rs index 7f98d28f..c66ae43e 100644 --- a/yellowstone-grpc-proto/build.rs +++ b/yellowstone-grpc-proto/build.rs @@ -17,7 +17,7 @@ fn main() -> anyhow::Result<()> { .input_type("crate::geyser::SubscribeRequest") // .output_type("crate::geyser::SubscribeUpdate") .codec_path("tonic::codec::ProstCodec") - .output_type("crate::plugin::filter::Message") + .output_type("crate::plugin::message_ref::Message") // .codec_path("crate::plugin::codec::SubscribeCodec") .client_streaming() .server_streaming() @@ -82,15 +82,15 @@ fn main() -> anyhow::Result<()> { .build_client(false) .compile(&[geyser_service]); - // patching generated custom struct - let mut location = std::path::PathBuf::from(std::env::var("OUT_DIR")?); - location.push("geyser.Geyser.rs"); - let geyser_rs = std::fs::read_to_string(location.clone())?; - let geyser_rs = geyser_rs.replace( - "let codec = crate::geyser_weak::codec::SubscribeCodec::default();", - "let codec = crate::geyser_weak::codec::SubscribeCodec::::default();", - ); - std::fs::write(location, geyser_rs)?; + // patching generated custom struct (if custom Codec is used) + // let mut location = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + // location.push("geyser.Geyser.rs"); + // let geyser_rs = std::fs::read_to_string(location.clone())?; + // let geyser_rs = geyser_rs.replace( + // "let codec = crate::plugin::codec::SubscribeCodec::default();", + // "let codec = crate::plugin::codec::SubscribeCodec::::default();", + // ); + // std::fs::write(location, geyser_rs)?; Ok(()) } diff --git a/yellowstone-grpc-proto/src/plugin/filter.rs b/yellowstone-grpc-proto/src/plugin/filter.rs index d55166b4..97033ddf 100644 --- a/yellowstone-grpc-proto/src/plugin/filter.rs +++ b/yellowstone-grpc-proto/src/plugin/filter.rs @@ -1,31 +1,8 @@ -use { - super::geyser::{ - MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, - MessageTransaction, MessageTransactionInfo, - }, - crate::{ - convert_to, - geyser::{ - subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateBlockMeta, - SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, - }, - }, - bytes::buf::{Buf, BufMut}, - prost::{ - encoding::{ - encode_key, encode_varint, encoded_len_varint, key_len, message, DecodeContext, - WireType, - }, - DecodeError, - }, - smallvec::SmallVec, - std::{ - borrow::Borrow, - collections::HashSet, - ops::Range, - sync::Arc, - time::{Duration, Instant}, - }, +use std::{ + borrow::Borrow, + collections::HashSet, + sync::Arc, + time::{Duration, Instant}, }; #[derive(Debug, thiserror::Error)] @@ -114,612 +91,3 @@ impl FilterNames { } } } - -#[derive(Debug)] -pub struct Message { - pub filters: MessageFilters, // 1 - pub message: MessageWeak, // 2, 3, 4, 10, 5, 6, 9, 7, 8 -} - -impl From<&Message> for SubscribeUpdate { - fn from(message: &Message) -> Self { - SubscribeUpdate { - filters: message - .filters - .iter() - .map(|f| f.as_ref().to_owned()) - .collect(), - update_oneof: Some((&message.message).into()), - } - } -} - -impl prost::Message for Message { - fn encode_raw(&self, buf: &mut impl BufMut) { - for name in self.filters.iter().map(|filter| filter.as_ref()) { - encode_key(1u32, WireType::LengthDelimited, buf); - encode_varint(name.len() as u64, buf); - buf.put_slice(name.as_bytes()); - } - self.message.encode_raw(buf) - } - - fn encoded_len(&self) -> usize { - key_len(1u32) * self.filters.len() - + self - .filters - .iter() - .map(|filter| { - encoded_len_varint(filter.as_ref().len() as u64) + filter.as_ref().len() - }) - .sum::() - + self.message.encoded_len() - } - - fn merge_field( - &mut self, - _tag: u32, - _wire_type: WireType, - _buf: &mut impl Buf, - _ctx: DecodeContext, - ) -> Result<(), DecodeError> { - unimplemented!() - } - - fn clear(&mut self) { - unimplemented!() - } -} - -impl Message { - pub fn new(filters: MessageFilters, message: MessageWeak) -> Self { - Self { filters, message } - } -} - -pub type MessageFilters = SmallVec<[FilterName; 4]>; - -#[derive(Debug)] -pub enum MessageWeak { - Account, // 2 - Slot, // 3 - Transaction, // 4 - TransactionStatus, // 10 - Block, // 5 - Ping, // 6 - Pong(MessageWeakPong), // 9 - BlockMeta(MessageWeakBlockMeta), // 7 - Entry(MessageWeakEntry), // 8 -} - -impl From<&MessageWeak> for UpdateOneof { - fn from(message: &MessageWeak) -> Self { - match message { - MessageWeak::Account => todo!(), - MessageWeak::Slot => todo!(), - MessageWeak::Transaction => todo!(), - MessageWeak::TransactionStatus => todo!(), - MessageWeak::Block => todo!(), - MessageWeak::Ping => Self::Ping(SubscribeUpdatePing {}), - MessageWeak::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), - MessageWeak::BlockMeta(msg) => Self::BlockMeta(msg.into()), - MessageWeak::Entry(msg) => Self::Entry(msg.into()), - } - } -} - -impl prost::Message for MessageWeak { - fn encode_raw(&self, buf: &mut impl BufMut) { - match self { - MessageWeak::Account => todo!(), - MessageWeak::Slot => todo!(), - MessageWeak::Transaction => todo!(), - MessageWeak::TransactionStatus => todo!(), - MessageWeak::Block => todo!(), - MessageWeak::Ping => { - encode_key(6u32, WireType::LengthDelimited, buf); - encode_varint(0, buf); - } - MessageWeak::Pong(msg) => message::encode(9u32, msg, buf), - MessageWeak::BlockMeta(msg) => message::encode(7u32, msg, buf), - MessageWeak::Entry(msg) => message::encode(8u32, msg, buf), - } - } - - fn encoded_len(&self) -> usize { - match self { - MessageWeak::Account => todo!(), - MessageWeak::Slot => todo!(), - MessageWeak::Transaction => todo!(), - MessageWeak::TransactionStatus => todo!(), - MessageWeak::Block => todo!(), - MessageWeak::Ping => 0, - MessageWeak::Pong(msg) => message::encoded_len(9u32, msg), - MessageWeak::BlockMeta(msg) => message::encoded_len(7u32, msg), - MessageWeak::Entry(msg) => message::encoded_len(8u32, msg), - } - } - - fn merge_field( - &mut self, - _tag: u32, - _wire_type: WireType, - _buf: &mut impl Buf, - _ctx: DecodeContext, - ) -> Result<(), DecodeError> { - unimplemented!() - } - - fn clear(&mut self) { - unimplemented!() - } -} - -impl MessageWeak { - pub fn account(message: &MessageAccount, accounts_data_slice: Vec>) -> Self { - todo!() - } - - pub fn slot(message: MessageSlot) -> Self { - todo!() - } - - pub fn transaction(message: &MessageTransaction) -> Self { - todo!() - } - - pub fn transaction_status(message: &MessageTransaction) -> Self { - todo!() - } - - pub fn block(message: MessageWeakBlock) -> Self { - todo!() - } - - pub const fn pong(id: i32) -> Self { - Self::Pong(MessageWeakPong { id }) - } - - pub fn block_meta(message: &Arc) -> Self { - Self::BlockMeta(MessageWeakBlockMeta(Arc::clone(message))) - } - - pub fn entry(message: &Arc) -> Self { - Self::Entry(MessageWeakEntry(Arc::clone(message))) - } -} - -#[derive(Debug)] -pub struct MessageWeakBlock { - pub meta: Arc, - pub transactions: Vec>, - pub updated_account_count: u64, - pub accounts: Vec>, - pub accounts_data_slice: Vec>, - pub entries: Vec>, -} - -#[derive(prost::Message)] -pub struct MessageWeakPong { - #[prost(int32, tag = "1")] - pub id: i32, -} - -#[derive(Debug)] -pub struct MessageWeakBlockMeta(pub Arc); - -impl From<&MessageWeakBlockMeta> for SubscribeUpdateBlockMeta { - fn from(MessageWeakBlockMeta(msg): &MessageWeakBlockMeta) -> Self { - Self { - slot: msg.slot, - blockhash: msg.blockhash.clone(), - rewards: Some(convert_to::create_rewards_obj( - msg.rewards.as_slice(), - msg.num_partitions, - )), - block_time: msg.block_time.map(convert_to::create_timestamp), - block_height: msg.block_height.map(convert_to::create_block_height), - parent_slot: msg.parent_slot, - parent_blockhash: msg.parent_blockhash.clone(), - executed_transaction_count: msg.executed_transaction_count, - entries_count: msg.entries_count, - } - } -} - -impl prost::Message for MessageWeakBlockMeta { - fn encode_raw(&self, buf: &mut impl BufMut) { - // let msg = &self.0; - // if self.slot != 0u64 { - // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); - // } - // if self.blockhash != "" { - // ::prost::encoding::string::encode(2u32, &self.blockhash, buf); - // } - // if let Some(ref msg) = self.rewards { - // ::prost::encoding::message::encode(3u32, msg, buf); - // } - // if let Some(ref msg) = self.block_time { - // ::prost::encoding::message::encode(4u32, msg, buf); - // } - // if let Some(ref msg) = self.block_height { - // ::prost::encoding::message::encode(5u32, msg, buf); - // } - // if self.parent_slot != 0u64 { - // ::prost::encoding::uint64::encode(6u32, &self.parent_slot, buf); - // } - // if self.parent_blockhash != "" { - // ::prost::encoding::string::encode(7u32, &self.parent_blockhash, buf); - // } - // if self.executed_transaction_count != 0u64 { - // ::prost::encoding::uint64::encode( - // 8u32, - // &self.executed_transaction_count, - // buf, - // ); - // } - // if self.entries_count != 0u64 { - // ::prost::encoding::uint64::encode(9u32, &self.entries_count, buf); - // } - todo!() - } - - fn encoded_len(&self) -> usize { - // let msg = &self.0; - // (if msg.slot != 0u64 { - // ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) - // } else { - // 0 - // }) + if msg.blockhash != "" { - // ::prost::encoding::string::encoded_len(2u32, &msg.blockhash) - // } else { - // 0 - // } + self - // .rewards - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(3u32, msg)) - // + self - // .block_time - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) - // + self - // .block_height - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) - // + if msg.parent_slot != 0u64 { - // ::prost::encoding::uint64::encoded_len(6u32, &msg.parent_slot) - // } else { - // 0 - // } - // + if msg.parent_blockhash != "" { - // ::prost::encoding::string::encoded_len(7u32, &msg.parent_blockhash) - // } else { - // 0 - // } - // + if msg.executed_transaction_count != 0u64 { - // ::prost::encoding::uint64::encoded_len(8u32, &msg.executed_transaction_count) - // } else { - // 0 - // } - // + if msg.entries_count != 0u64 { - // ::prost::encoding::uint64::encoded_len(9u32, &msg.entries_count) - // } else { - // 0 - // } - todo!() - } - - fn merge_field( - &mut self, - _tag: u32, - _wire_type: WireType, - _buf: &mut impl Buf, - _ctx: DecodeContext, - ) -> Result<(), DecodeError> { - unimplemented!() - } - - fn clear(&mut self) { - unimplemented!() - } -} - -#[derive(Debug)] -pub struct MessageWeakEntry(pub Arc); - -impl From<&MessageWeakEntry> for SubscribeUpdateEntry { - fn from(MessageWeakEntry(msg): &MessageWeakEntry) -> Self { - Self { - slot: msg.slot, - index: msg.index as u64, - num_hashes: msg.num_hashes, - hash: msg.hash.into(), - executed_transaction_count: msg.executed_transaction_count, - starting_transaction_index: msg.starting_transaction_index, - } - } -} - -impl prost::Message for MessageWeakEntry { - fn encode_raw(&self, buf: &mut impl BufMut) { - let msg = &self.0; - let index = msg.index as u64; - if msg.slot != 0u64 { - ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); - } - if index != 0u64 { - ::prost::encoding::uint64::encode(2u32, &index, buf); - } - if msg.num_hashes != 0u64 { - ::prost::encoding::uint64::encode(3u32, &msg.num_hashes, buf); - } - if !msg.hash.is_empty() { - prost_bytes_encode_raw(4u32, &msg.hash, buf); - } - if msg.executed_transaction_count != 0u64 { - ::prost::encoding::uint64::encode(5u32, &msg.executed_transaction_count, buf); - } - if msg.starting_transaction_index != 0u64 { - ::prost::encoding::uint64::encode(6u32, &msg.starting_transaction_index, buf); - } - } - - fn encoded_len(&self) -> usize { - let msg = &self.0; - let index = msg.index as u64; - (if msg.slot != 0u64 { - ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) - } else { - 0 - }) + if index != 0u64 { - ::prost::encoding::uint64::encoded_len(2u32, &index) - } else { - 0 - } + if msg.num_hashes != 0u64 { - ::prost::encoding::uint64::encoded_len(3u32, &msg.num_hashes) - } else { - 0 - } + if !msg.hash.is_empty() { - prost_bytes_encoded_len(4u32, &msg.hash) - } else { - 0 - } + if msg.executed_transaction_count != 0u64 { - ::prost::encoding::uint64::encoded_len(5u32, &msg.executed_transaction_count) - } else { - 0 - } + if msg.starting_transaction_index != 0u64 { - ::prost::encoding::uint64::encoded_len(6u32, &msg.starting_transaction_index) - } else { - 0 - } - } - - fn merge_field( - &mut self, - _tag: u32, - _wire_type: WireType, - _buf: &mut impl Buf, - _ctx: DecodeContext, - ) -> Result<(), DecodeError> { - unimplemented!() - } - - fn clear(&mut self) { - unimplemented!() - } -} - -#[inline] -fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { - encode_key(tag, WireType::LengthDelimited, buf); - encode_varint(value.len() as u64, buf); - buf.put(value); -} - -#[inline] -pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { - key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() -} - -#[cfg(test)] -mod tests { - use { - super::{FilterName, Message, MessageEntry, MessageFilters, MessageWeak}, - crate::geyser::SubscribeUpdate, - prost::Message as _, - std::sync::Arc, - }; - - fn create_message_filters(names: &[&str]) -> MessageFilters { - let mut filters = MessageFilters::new(); - for name in names { - filters.push(FilterName::new(*name)); - } - filters - } - - fn encode_decode_cmp(filters: &[&str], message: MessageWeak) { - let msg = Message { - filters: create_message_filters(filters), - message, - }; - // println!("{:?}", SubscribeUpdate::from(&msg)); - let bytes = msg.encode_to_vec(); - let update = SubscribeUpdate::decode(bytes.as_slice()).expect("failed to decode"); - // println!("{update:?}"); - assert_eq!(update, SubscribeUpdate::from(&msg)); - } - - #[test] - fn test_message_ping() { - encode_decode_cmp(&["123"], MessageWeak::Ping) - } - - #[test] - fn test_message_pong() { - encode_decode_cmp(&["123"], MessageWeak::pong(0)); - encode_decode_cmp(&["123"], MessageWeak::pong(42)); - } - - #[test] - fn test_message_entry() { - encode_decode_cmp( - &["123"], - MessageWeak::entry(&Arc::new(MessageEntry { - slot: 299888121, - index: 42, - num_hashes: 128, - hash: [98; 32], - executed_transaction_count: 32, - starting_transaction_index: 1000, - })), - ); - } -} - -// #[derive(Debug, Clone)] -// pub enum FilteredMessage2<'a> { -// Slot(&'a MessageSlot), -// Account(&'a MessageAccount), -// Transaction(&'a MessageTransaction), -// TransactionStatus(&'a MessageTransaction), -// Entry(&'a MessageEntry), -// Block(MessageBlock), -// BlockMeta(&'a MessageBlockMeta), -// } - -// impl<'a> FilteredMessage2<'a> { -// fn as_proto_account( -// message: &MessageAccountInfo, -// accounts_data_slice: &[Range], -// ) -> SubscribeUpdateAccountInfo { -// let data = if accounts_data_slice.is_empty() { -// message.data.clone() -// } else { -// let mut data = -// Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); -// for slice in accounts_data_slice { -// if message.data.len() >= slice.end { -// data.extend_from_slice(&message.data[slice.start..slice.end]); -// } -// } -// data -// }; -// SubscribeUpdateAccountInfo { -// pubkey: message.pubkey.as_ref().into(), -// lamports: message.lamports, -// owner: message.owner.as_ref().into(), -// executable: message.executable, -// rent_epoch: message.rent_epoch, -// data, -// write_version: message.write_version, -// txn_signature: message.txn_signature.map(|s| s.as_ref().into()), -// } -// } - -// fn as_proto_transaction(message: &MessageTransactionInfo) -> SubscribeUpdateTransactionInfo { -// SubscribeUpdateTransactionInfo { -// signature: message.signature.as_ref().into(), -// is_vote: message.is_vote, -// transaction: Some(convert_to::create_transaction(&message.transaction)), -// meta: Some(convert_to::create_transaction_meta(&message.meta)), -// index: message.index as u64, -// } -// } - -// fn as_proto_entry(message: &MessageEntry) -> SubscribeUpdateEntry { -// SubscribeUpdateEntry { -// slot: message.slot, -// index: message.index as u64, -// num_hashes: message.num_hashes, -// hash: message.hash.into(), -// executed_transaction_count: message.executed_transaction_count, -// starting_transaction_index: message.starting_transaction_index, -// } -// } - -// pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { -// match self { -// Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { -// slot: message.slot, -// parent: message.parent, -// status: message.status as i32, -// }), -// Self::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount { -// account: Some(Self::as_proto_account( -// message.account.as_ref(), -// accounts_data_slice, -// )), -// slot: message.slot, -// is_startup: message.is_startup, -// }), -// Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction { -// transaction: Some(Self::as_proto_transaction(message.transaction.as_ref())), -// slot: message.slot, -// }), -// Self::TransactionStatus(message) => { -// UpdateOneof::TransactionStatus(SubscribeUpdateTransactionStatus { -// slot: message.slot, -// signature: message.transaction.signature.as_ref().into(), -// is_vote: message.transaction.is_vote, -// index: message.transaction.index as u64, -// err: match &message.transaction.meta.status { -// Ok(()) => None, -// Err(err) => Some(SubscribeUpdateTransactionError { -// err: bincode::serialize(&err) -// .expect("transaction error to serialize to bytes"), -// }), -// }, -// }) -// } -// Self::Entry(message) => UpdateOneof::Entry(Self::as_proto_entry(message)), -// Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { -// slot: message.meta.slot, -// blockhash: message.meta.blockhash.clone(), -// rewards: Some(convert_to::create_rewards_obj( -// message.meta.rewards.as_slice(), -// message.meta.num_partitions, -// )), -// block_time: message.meta.block_time.map(convert_to::create_timestamp), -// block_height: message -// .meta -// .block_height -// .map(convert_to::create_block_height), -// parent_slot: message.meta.parent_slot, -// parent_blockhash: message.meta.parent_blockhash.clone(), -// executed_transaction_count: message.meta.executed_transaction_count, -// transactions: message -// .transactions -// .iter() -// .map(|tx| Self::as_proto_transaction(tx.as_ref())) -// .collect(), -// updated_account_count: message.updated_account_count, -// accounts: message -// .accounts -// .iter() -// .map(|acc| Self::as_proto_account(acc.as_ref(), accounts_data_slice)) -// .collect(), -// entries_count: message.meta.entries_count, -// entries: message -// .entries -// .iter() -// .map(|entry| Self::as_proto_entry(entry.as_ref())) -// .collect(), -// }), -// Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { -// slot: message.slot, -// blockhash: message.blockhash.clone(), -// rewards: Some(convert_to::create_rewards_obj( -// message.rewards.as_slice(), -// message.num_partitions, -// )), -// 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, -// entries_count: message.entries_count, -// }), -// } -// } -// } diff --git a/yellowstone-grpc-proto/src/plugin/geyser.rs b/yellowstone-grpc-proto/src/plugin/message.rs similarity index 100% rename from yellowstone-grpc-proto/src/plugin/geyser.rs rename to yellowstone-grpc-proto/src/plugin/message.rs diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs new file mode 100644 index 00000000..4583fee6 --- /dev/null +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -0,0 +1,635 @@ +use { + super::{ + filter::FilterName, + message::{ + MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, + MessageTransaction, MessageTransactionInfo, + }, + }, + crate::{ + convert_to, + geyser::{ + subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateBlockMeta, + SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, + }, + }, + bytes::buf::{Buf, BufMut}, + prost::{ + encoding::{ + encode_key, encode_varint, encoded_len_varint, key_len, message, DecodeContext, + WireType, + }, + DecodeError, + }, + smallvec::SmallVec, + std::{ops::Range, sync::Arc}, +}; + +#[derive(Debug)] +pub struct Message { + pub filters: MessageFilters, + pub message: MessageRef, +} + +impl From<&Message> for SubscribeUpdate { + fn from(message: &Message) -> Self { + SubscribeUpdate { + filters: message + .filters + .iter() + .map(|f| f.as_ref().to_owned()) + .collect(), + update_oneof: Some((&message.message).into()), + } + } +} + +impl prost::Message for Message { + fn encode_raw(&self, buf: &mut impl BufMut) { + for name in self.filters.iter().map(|filter| filter.as_ref()) { + encode_key(1u32, WireType::LengthDelimited, buf); + encode_varint(name.len() as u64, buf); + buf.put_slice(name.as_bytes()); + } + self.message.encode_raw(buf) + } + + fn encoded_len(&self) -> usize { + key_len(1u32) * self.filters.len() + + self + .filters + .iter() + .map(|filter| { + encoded_len_varint(filter.as_ref().len() as u64) + filter.as_ref().len() + }) + .sum::() + + self.message.encoded_len() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +impl Message { + pub fn new(filters: MessageFilters, message: MessageRef) -> Self { + Self { filters, message } + } +} + +pub type MessageFilters = SmallVec<[FilterName; 4]>; + +#[derive(Debug)] +pub enum MessageRef { + Account, // 2 + Slot, // 3 + Transaction, // 4 + TransactionStatus, // 10 + Block, // 5 + Ping, // 6 + Pong(MessageRefPong), // 9 + BlockMeta(MessageRefBlockMeta), // 7 + Entry(MessageRefEntry), // 8 +} + +impl From<&MessageRef> for UpdateOneof { + fn from(message: &MessageRef) -> Self { + match message { + MessageRef::Account => todo!(), + MessageRef::Slot => todo!(), + MessageRef::Transaction => todo!(), + MessageRef::TransactionStatus => todo!(), + MessageRef::Block => todo!(), + MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), + MessageRef::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), + MessageRef::BlockMeta(msg) => Self::BlockMeta(msg.into()), + MessageRef::Entry(msg) => Self::Entry(msg.into()), + } + } +} + +impl prost::Message for MessageRef { + fn encode_raw(&self, buf: &mut impl BufMut) { + match self { + MessageRef::Account => todo!(), + MessageRef::Slot => todo!(), + MessageRef::Transaction => todo!(), + MessageRef::TransactionStatus => todo!(), + MessageRef::Block => todo!(), + MessageRef::Ping => { + encode_key(6u32, WireType::LengthDelimited, buf); + encode_varint(0, buf); + } + MessageRef::Pong(msg) => message::encode(9u32, msg, buf), + MessageRef::BlockMeta(msg) => message::encode(7u32, msg, buf), + MessageRef::Entry(msg) => message::encode(8u32, msg, buf), + } + } + + fn encoded_len(&self) -> usize { + match self { + MessageRef::Account => todo!(), + MessageRef::Slot => todo!(), + MessageRef::Transaction => todo!(), + MessageRef::TransactionStatus => todo!(), + MessageRef::Block => todo!(), + MessageRef::Ping => 0, + MessageRef::Pong(msg) => message::encoded_len(9u32, msg), + MessageRef::BlockMeta(msg) => message::encoded_len(7u32, msg), + MessageRef::Entry(msg) => message::encoded_len(8u32, msg), + } + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +impl MessageRef { + pub fn account(message: &MessageAccount, accounts_data_slice: Vec>) -> Self { + todo!() + } + + pub fn slot(message: MessageSlot) -> Self { + todo!() + } + + pub fn transaction(message: &MessageTransaction) -> Self { + todo!() + } + + pub fn transaction_status(message: &MessageTransaction) -> Self { + todo!() + } + + pub fn block(message: MessageRefBlock) -> Self { + todo!() + } + + pub const fn pong(id: i32) -> Self { + Self::Pong(MessageRefPong { id }) + } + + pub fn block_meta(message: &Arc) -> Self { + Self::BlockMeta(MessageRefBlockMeta(Arc::clone(message))) + } + + pub fn entry(message: &Arc) -> Self { + Self::Entry(MessageRefEntry(Arc::clone(message))) + } +} + +#[derive(Debug)] +pub struct MessageRefBlock { + pub meta: Arc, + pub transactions: Vec>, + pub updated_account_count: u64, + pub accounts: Vec>, + pub accounts_data_slice: Vec>, + pub entries: Vec>, +} + +#[derive(prost::Message)] +pub struct MessageRefPong { + #[prost(int32, tag = "1")] + pub id: i32, +} + +#[derive(Debug)] +pub struct MessageRefBlockMeta(pub Arc); + +impl From<&MessageRefBlockMeta> for SubscribeUpdateBlockMeta { + fn from(MessageRefBlockMeta(msg): &MessageRefBlockMeta) -> Self { + Self { + slot: msg.slot, + blockhash: msg.blockhash.clone(), + rewards: Some(convert_to::create_rewards_obj( + msg.rewards.as_slice(), + msg.num_partitions, + )), + block_time: msg.block_time.map(convert_to::create_timestamp), + block_height: msg.block_height.map(convert_to::create_block_height), + parent_slot: msg.parent_slot, + parent_blockhash: msg.parent_blockhash.clone(), + executed_transaction_count: msg.executed_transaction_count, + entries_count: msg.entries_count, + } + } +} + +impl prost::Message for MessageRefBlockMeta { + fn encode_raw(&self, buf: &mut impl BufMut) { + // let msg = &self.0; + // if self.slot != 0u64 { + // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); + // } + // if self.blockhash != "" { + // ::prost::encoding::string::encode(2u32, &self.blockhash, buf); + // } + // if let Some(ref msg) = self.rewards { + // ::prost::encoding::message::encode(3u32, msg, buf); + // } + // if let Some(ref msg) = self.block_time { + // ::prost::encoding::message::encode(4u32, msg, buf); + // } + // if let Some(ref msg) = self.block_height { + // ::prost::encoding::message::encode(5u32, msg, buf); + // } + // if self.parent_slot != 0u64 { + // ::prost::encoding::uint64::encode(6u32, &self.parent_slot, buf); + // } + // if self.parent_blockhash != "" { + // ::prost::encoding::string::encode(7u32, &self.parent_blockhash, buf); + // } + // if self.executed_transaction_count != 0u64 { + // ::prost::encoding::uint64::encode( + // 8u32, + // &self.executed_transaction_count, + // buf, + // ); + // } + // if self.entries_count != 0u64 { + // ::prost::encoding::uint64::encode(9u32, &self.entries_count, buf); + // } + todo!() + } + + fn encoded_len(&self) -> usize { + // let msg = &self.0; + // (if msg.slot != 0u64 { + // ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + // } else { + // 0 + // }) + if msg.blockhash != "" { + // ::prost::encoding::string::encoded_len(2u32, &msg.blockhash) + // } else { + // 0 + // } + self + // .rewards + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(3u32, msg)) + // + self + // .block_time + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) + // + self + // .block_height + // .as_ref() + // .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) + // + if msg.parent_slot != 0u64 { + // ::prost::encoding::uint64::encoded_len(6u32, &msg.parent_slot) + // } else { + // 0 + // } + // + if msg.parent_blockhash != "" { + // ::prost::encoding::string::encoded_len(7u32, &msg.parent_blockhash) + // } else { + // 0 + // } + // + if msg.executed_transaction_count != 0u64 { + // ::prost::encoding::uint64::encoded_len(8u32, &msg.executed_transaction_count) + // } else { + // 0 + // } + // + if msg.entries_count != 0u64 { + // ::prost::encoding::uint64::encoded_len(9u32, &msg.entries_count) + // } else { + // 0 + // } + todo!() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +#[derive(Debug)] +pub struct MessageRefEntry(pub Arc); + +impl From<&MessageRefEntry> for SubscribeUpdateEntry { + fn from(MessageRefEntry(msg): &MessageRefEntry) -> Self { + Self { + slot: msg.slot, + index: msg.index as u64, + num_hashes: msg.num_hashes, + hash: msg.hash.into(), + executed_transaction_count: msg.executed_transaction_count, + starting_transaction_index: msg.starting_transaction_index, + } + } +} + +impl prost::Message for MessageRefEntry { + fn encode_raw(&self, buf: &mut impl BufMut) { + let msg = &self.0; + let index = msg.index as u64; + if msg.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); + } + if index != 0u64 { + ::prost::encoding::uint64::encode(2u32, &index, buf); + } + if msg.num_hashes != 0u64 { + ::prost::encoding::uint64::encode(3u32, &msg.num_hashes, buf); + } + if !msg.hash.is_empty() { + prost_bytes_encode_raw(4u32, &msg.hash, buf); + } + if msg.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encode(5u32, &msg.executed_transaction_count, buf); + } + if msg.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encode(6u32, &msg.starting_transaction_index, buf); + } + } + + fn encoded_len(&self) -> usize { + let msg = &self.0; + let index = msg.index as u64; + (if msg.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + } else { + 0 + }) + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &index) + } else { + 0 + } + if msg.num_hashes != 0u64 { + ::prost::encoding::uint64::encoded_len(3u32, &msg.num_hashes) + } else { + 0 + } + if !msg.hash.is_empty() { + prost_bytes_encoded_len(4u32, &msg.hash) + } else { + 0 + } + if msg.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &msg.executed_transaction_count) + } else { + 0 + } + if msg.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encoded_len(6u32, &msg.starting_transaction_index) + } else { + 0 + } + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +#[inline] +fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(value.len() as u64, buf); + buf.put(value); +} + +#[inline] +pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { + key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() +} + +#[cfg(test)] +mod tests { + use { + super::{FilterName, Message, MessageEntry, MessageFilters, MessageRef}, + crate::geyser::SubscribeUpdate, + prost::Message as _, + std::sync::Arc, + }; + + fn create_message_filters(names: &[&str]) -> MessageFilters { + let mut filters = MessageFilters::new(); + for name in names { + filters.push(FilterName::new(*name)); + } + filters + } + + fn encode_decode_cmp(filters: &[&str], message: MessageRef) { + let msg = Message { + filters: create_message_filters(filters), + message, + }; + // println!("{:?}", SubscribeUpdate::from(&msg)); + let bytes = msg.encode_to_vec(); + let update = SubscribeUpdate::decode(bytes.as_slice()).expect("failed to decode"); + // println!("{update:?}"); + assert_eq!(update, SubscribeUpdate::from(&msg)); + } + + #[test] + fn test_message_ping() { + encode_decode_cmp(&["123"], MessageRef::Ping) + } + + #[test] + fn test_message_pong() { + encode_decode_cmp(&["123"], MessageRef::pong(0)); + encode_decode_cmp(&["123"], MessageRef::pong(42)); + } + + #[test] + fn test_message_entry() { + encode_decode_cmp( + &["123"], + MessageRef::entry(&Arc::new(MessageEntry { + slot: 299888121, + index: 42, + num_hashes: 128, + hash: [98; 32], + executed_transaction_count: 32, + starting_transaction_index: 1000, + })), + ); + } +} + +// #[derive(Debug, Clone)] +// pub enum FilteredMessage2<'a> { +// Slot(&'a MessageSlot), +// Account(&'a MessageAccount), +// Transaction(&'a MessageTransaction), +// TransactionStatus(&'a MessageTransaction), +// Entry(&'a MessageEntry), +// Block(MessageBlock), +// BlockMeta(&'a MessageBlockMeta), +// } + +// impl<'a> FilteredMessage2<'a> { +// fn as_proto_account( +// message: &MessageAccountInfo, +// accounts_data_slice: &[Range], +// ) -> SubscribeUpdateAccountInfo { +// let data = if accounts_data_slice.is_empty() { +// message.data.clone() +// } else { +// let mut data = +// Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); +// for slice in accounts_data_slice { +// if message.data.len() >= slice.end { +// data.extend_from_slice(&message.data[slice.start..slice.end]); +// } +// } +// data +// }; +// SubscribeUpdateAccountInfo { +// pubkey: message.pubkey.as_ref().into(), +// lamports: message.lamports, +// owner: message.owner.as_ref().into(), +// executable: message.executable, +// rent_epoch: message.rent_epoch, +// data, +// write_version: message.write_version, +// txn_signature: message.txn_signature.map(|s| s.as_ref().into()), +// } +// } + +// fn as_proto_transaction(message: &MessageTransactionInfo) -> SubscribeUpdateTransactionInfo { +// SubscribeUpdateTransactionInfo { +// signature: message.signature.as_ref().into(), +// is_vote: message.is_vote, +// transaction: Some(convert_to::create_transaction(&message.transaction)), +// meta: Some(convert_to::create_transaction_meta(&message.meta)), +// index: message.index as u64, +// } +// } + +// fn as_proto_entry(message: &MessageEntry) -> SubscribeUpdateEntry { +// SubscribeUpdateEntry { +// slot: message.slot, +// index: message.index as u64, +// num_hashes: message.num_hashes, +// hash: message.hash.into(), +// executed_transaction_count: message.executed_transaction_count, +// starting_transaction_index: message.starting_transaction_index, +// } +// } + +// pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { +// match self { +// Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { +// slot: message.slot, +// parent: message.parent, +// status: message.status as i32, +// }), +// Self::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount { +// account: Some(Self::as_proto_account( +// message.account.as_ref(), +// accounts_data_slice, +// )), +// slot: message.slot, +// is_startup: message.is_startup, +// }), +// Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction { +// transaction: Some(Self::as_proto_transaction(message.transaction.as_ref())), +// slot: message.slot, +// }), +// Self::TransactionStatus(message) => { +// UpdateOneof::TransactionStatus(SubscribeUpdateTransactionStatus { +// slot: message.slot, +// signature: message.transaction.signature.as_ref().into(), +// is_vote: message.transaction.is_vote, +// index: message.transaction.index as u64, +// err: match &message.transaction.meta.status { +// Ok(()) => None, +// Err(err) => Some(SubscribeUpdateTransactionError { +// err: bincode::serialize(&err) +// .expect("transaction error to serialize to bytes"), +// }), +// }, +// }) +// } +// Self::Entry(message) => UpdateOneof::Entry(Self::as_proto_entry(message)), +// Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { +// slot: message.meta.slot, +// blockhash: message.meta.blockhash.clone(), +// rewards: Some(convert_to::create_rewards_obj( +// message.meta.rewards.as_slice(), +// message.meta.num_partitions, +// )), +// block_time: message.meta.block_time.map(convert_to::create_timestamp), +// block_height: message +// .meta +// .block_height +// .map(convert_to::create_block_height), +// parent_slot: message.meta.parent_slot, +// parent_blockhash: message.meta.parent_blockhash.clone(), +// executed_transaction_count: message.meta.executed_transaction_count, +// transactions: message +// .transactions +// .iter() +// .map(|tx| Self::as_proto_transaction(tx.as_ref())) +// .collect(), +// updated_account_count: message.updated_account_count, +// accounts: message +// .accounts +// .iter() +// .map(|acc| Self::as_proto_account(acc.as_ref(), accounts_data_slice)) +// .collect(), +// entries_count: message.meta.entries_count, +// entries: message +// .entries +// .iter() +// .map(|entry| Self::as_proto_entry(entry.as_ref())) +// .collect(), +// }), +// Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { +// slot: message.slot, +// blockhash: message.blockhash.clone(), +// rewards: Some(convert_to::create_rewards_obj( +// message.rewards.as_slice(), +// message.num_partitions, +// )), +// 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, +// entries_count: message.entries_count, +// }), +// } +// } +// } diff --git a/yellowstone-grpc-proto/src/plugin/mod.rs b/yellowstone-grpc-proto/src/plugin/mod.rs index 96179dcb..2d5c06d1 100644 --- a/yellowstone-grpc-proto/src/plugin/mod.rs +++ b/yellowstone-grpc-proto/src/plugin/mod.rs @@ -1,5 +1,6 @@ pub mod filter; -pub mod geyser; +pub mod message; +pub mod message_ref; pub mod proto { #![allow(clippy::clone_on_ref_ptr)] From b6f947b0f0dad8f62df5047ca3ecf6db4d1b9873 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 11:12:27 +0200 Subject: [PATCH 18/50] add predefined --- .gitignore | 1 + Cargo.lock | 157 ++++++++++++++++-- Cargo.toml | 2 + yellowstone-grpc-proto/Cargo.toml | 4 + .../src/plugin/blocks/18144001.bincode | Bin 0 -> 35760 bytes .../src/plugin/blocks/43200.bincode | Bin 0 -> 1496 bytes .../src/plugin/blocks/43200000.bincode | Bin 0 -> 101014 bytes .../src/plugin/message_ref.rs | 51 ++++-- 8 files changed, 190 insertions(+), 25 deletions(-) create mode 100644 yellowstone-grpc-proto/src/plugin/blocks/18144001.bincode create mode 100644 yellowstone-grpc-proto/src/plugin/blocks/43200.bincode create mode 100644 yellowstone-grpc-proto/src/plugin/blocks/43200000.bincode diff --git a/.gitignore b/.gitignore index 2fdec21a..ace60611 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ test-ledger # Rust target +yellowstone-grpc-proto/src/bin/raw.rs # Node.js examples/typescript/dist diff --git a/Cargo.lock b/Cargo.lock index 4f234802..6c4e6cdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,7 +792,7 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.72", @@ -1483,6 +1483,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1540,6 +1546,15 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "hostname" version = "0.4.0" @@ -2024,6 +2039,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "multimap" version = "0.10.0" @@ -2324,6 +2345,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "prettyplease" version = "0.2.20" @@ -2400,6 +2431,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + [[package]] name = "prost" version = "0.13.1" @@ -2407,7 +2448,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.1", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap 0.8.3", + "petgraph", + "prettyplease 0.1.25", + "prost 0.11.9", + "prost-types 0.11.9", + "regex", + "syn 1.0.109", + "tempfile", + "which", ] [[package]] @@ -2417,20 +2480,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" dependencies = [ "bytes", - "heck", + "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.0", "once_cell", "petgraph", - "prettyplease", - "prost", - "prost-types", + "prettyplease 0.2.20", + "prost 0.13.1", + "prost-types 0.13.1", "regex", "syn 2.0.72", "tempfile", ] +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "prost-derive" version = "0.13.1" @@ -2444,13 +2520,22 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + [[package]] name = "prost-types" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" dependencies = [ - "prost", + "prost 0.13.1", ] [[package]] @@ -3264,6 +3349,23 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" +[[package]] +name = "solana-storage-proto" +version = "2.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bedde2051ddfa8408a504db80a3007f7b0e9aba537ace9dd06c2187084e0a1a" +dependencies = [ + "bincode", + "bs58", + "prost 0.11.9", + "protobuf-src", + "serde", + "solana-account-decoder", + "solana-sdk", + "solana-transaction-status", + "tonic-build 0.9.2", +] + [[package]] name = "solana-transaction-status" version = "2.0.10" @@ -3896,7 +3998,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", + "prost 0.13.1", "rustls-native-certs", "rustls-pemfile 2.1.3", "socket2", @@ -3910,15 +4012,28 @@ dependencies = [ "zstd 0.13.2", ] +[[package]] +name = "tonic-build" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +dependencies = [ + "prettyplease 0.1.25", + "proc-macro2", + "prost-build 0.11.9", + "quote", + "syn 1.0.109", +] + [[package]] name = "tonic-build" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568392c5a2bd0020723e3f387891176aabafe36fd9fcd074ad309dfa0c8eb964" dependencies = [ - "prettyplease", + "prettyplease 0.2.20", "proc-macro2", - "prost-build", + "prost-build 0.13.1", "quote", "syn 2.0.72", ] @@ -3930,7 +4045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e10e6a96ee08b6ce443487d4368442d328d0e746f3681f81127f7dc41b4955" dependencies = [ "async-stream", - "prost", + "prost 0.13.1", "tokio", "tokio-stream", "tonic", @@ -4225,6 +4340,18 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4525,15 +4652,17 @@ dependencies = [ "anyhow", "bincode", "bytes", - "prost", + "prost 0.11.9", + "prost 0.13.1", "protobuf-src", "smallvec", "solana-account-decoder", "solana-sdk", + "solana-storage-proto", "solana-transaction-status", "thiserror", "tonic", - "tonic-build", + "tonic-build 0.12.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4fbe7ad3..8ad29f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ log = "0.4.17" maplit = "1.0.2" prometheus = "0.13.2" prost = "0.13.1" +prost_011 = { package = "prost", version = "0.11.9" } protobuf-src = "1.1.0" scylla = "0.13.0" serde = "1.0.145" @@ -51,6 +52,7 @@ serde_json = "1.0.86" solana-account-decoder = "~2.0.10" solana-logger = "~2.0.10" solana-sdk = "~2.0.10" +solana-storage-proto = "~2.0.10" solana-transaction-status = "~2.0.10" smallvec = "1.13.2" spl-token-2022 = "4.0.0" diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index b9d1721e..85508561 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -22,6 +22,10 @@ solana-transaction-status = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } tonic = { workspace = true } +[dev-dependencies] +prost_011 = { workspace = true } +solana-storage-proto = { workspace = true } + [build-dependencies] anyhow = { workspace = true } protobuf-src = { workspace = true } diff --git a/yellowstone-grpc-proto/src/plugin/blocks/18144001.bincode b/yellowstone-grpc-proto/src/plugin/blocks/18144001.bincode new file mode 100644 index 0000000000000000000000000000000000000000..3d0a65b71bba01a04dc4f8b20f2e4502c357d4d7 GIT binary patch literal 35760 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

GT@z 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 Uh%#&$NCl%28^_;w`~I*30KMdzf&c&j literal 0 HcmV?d00001 diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 4583fee6..fe38ebee 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -448,6 +448,30 @@ mod tests { filters } + fn create_entries() -> Vec> { + [ + MessageEntry { + slot: 299888121, + index: 42, + num_hashes: 128, + hash: [98; 32], + executed_transaction_count: 32, + starting_transaction_index: 1000, + }, + MessageEntry { + slot: 299888121, + index: 0, + num_hashes: 16, + hash: [42; 32], + executed_transaction_count: 32, + starting_transaction_index: 1000, + }, + ] + .into_iter() + .map(Arc::new) + .collect() + } + fn encode_decode_cmp(filters: &[&str], message: MessageRef) { let msg = Message { filters: create_message_filters(filters), @@ -473,17 +497,22 @@ mod tests { #[test] fn test_message_entry() { - encode_decode_cmp( - &["123"], - MessageRef::entry(&Arc::new(MessageEntry { - slot: 299888121, - index: 42, - num_hashes: 128, - hash: [98; 32], - executed_transaction_count: 32, - starting_transaction_index: 1000, - })), - ); + for entry in create_entries() { + encode_decode_cmp(&["123"], MessageRef::entry(&entry)); + } + } + + #[test] + fn test_predefined() { + use {prost_011::Message as _, solana_storage_proto::convert::generated, std::fs}; + + let location = "./src/plugin/blocks"; + for entry in fs::read_dir(location).expect("failed to read `blocks` dir") { + let path = entry.expect("failed to read `blocks` dir entry").path(); + let data = fs::read(path).expect("failed to read block"); + let block = + generated::ConfirmedBlock::decode(data.as_slice()).expect("failed to decode block"); + } } } From 795caed816133caa67281e61e32c62edf9eeb493 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 11:43:30 +0200 Subject: [PATCH 19/50] encode slot --- yellowstone-grpc-proto/src/plugin/message.rs | 26 +++-- .../src/plugin/message_ref.rs | 102 ++++++++++++++++-- 2 files changed, 110 insertions(+), 18 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message.rs b/yellowstone-grpc-proto/src/plugin/message.rs index e3d71f75..3eaa9cee 100644 --- a/yellowstone-grpc-proto/src/plugin/message.rs +++ b/yellowstone-grpc-proto/src/plugin/message.rs @@ -29,14 +29,13 @@ impl From for CommitmentLevel { } } -impl PartialEq for CommitmentLevel { - fn eq(&self, other: &CommitmentLevelProto) -> bool { - matches!( - (self, other), - (Self::Processed, CommitmentLevelProto::Processed) - | (Self::Confirmed, CommitmentLevelProto::Confirmed) - | (Self::Finalized, CommitmentLevelProto::Finalized) - ) +impl From for CommitmentLevelProto { + fn from(commitment: CommitmentLevel) -> Self { + match commitment { + CommitmentLevel::Processed => Self::Processed, + CommitmentLevel::Confirmed => Self::Confirmed, + CommitmentLevel::Finalized => Self::Finalized, + } } } @@ -50,6 +49,17 @@ impl From for CommitmentLevel { } } +impl PartialEq for CommitmentLevel { + fn eq(&self, other: &CommitmentLevelProto) -> bool { + matches!( + (self, other), + (Self::Processed, CommitmentLevelProto::Processed) + | (Self::Confirmed, CommitmentLevelProto::Confirmed) + | (Self::Finalized, CommitmentLevelProto::Finalized) + ) + } +} + #[derive(Debug, Clone, Copy)] pub struct MessageSlot { pub slot: u64, diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index fe38ebee..95637263 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -9,8 +9,9 @@ use { crate::{ convert_to, geyser::{ - subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateBlockMeta, - SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, + subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, + SubscribeUpdate, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, + SubscribeUpdatePong, SubscribeUpdateSlot, }, }, bytes::buf::{Buf, BufMut}, @@ -92,7 +93,7 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; #[derive(Debug)] pub enum MessageRef { Account, // 2 - Slot, // 3 + Slot(MessageRefSlot), // 3 Transaction, // 4 TransactionStatus, // 10 Block, // 5 @@ -106,7 +107,7 @@ impl From<&MessageRef> for UpdateOneof { fn from(message: &MessageRef) -> Self { match message { MessageRef::Account => todo!(), - MessageRef::Slot => todo!(), + MessageRef::Slot(msg) => Self::Slot(msg.into()), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), MessageRef::Block => todo!(), @@ -122,7 +123,7 @@ impl prost::Message for MessageRef { fn encode_raw(&self, buf: &mut impl BufMut) { match self { MessageRef::Account => todo!(), - MessageRef::Slot => todo!(), + MessageRef::Slot(msg) => message::encode(3u32, msg, buf), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), MessageRef::Block => todo!(), @@ -139,7 +140,7 @@ impl prost::Message for MessageRef { fn encoded_len(&self) -> usize { match self { MessageRef::Account => todo!(), - MessageRef::Slot => todo!(), + MessageRef::Slot(msg) => message::encoded_len(3u32, msg), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), MessageRef::Block => todo!(), @@ -170,8 +171,8 @@ impl MessageRef { todo!() } - pub fn slot(message: MessageSlot) -> Self { - todo!() + pub const fn slot(message: MessageSlot) -> Self { + Self::Slot(MessageRefSlot(message)) } pub fn transaction(message: &MessageTransaction) -> Self { @@ -199,6 +200,65 @@ impl MessageRef { } } +#[derive(Debug)] +pub struct MessageRefSlot(pub MessageSlot); + +impl From<&MessageRefSlot> for SubscribeUpdateSlot { + fn from(MessageRefSlot(msg): &MessageRefSlot) -> Self { + Self { + slot: msg.slot, + parent: msg.parent, + status: CommitmentLevelProto::from(msg.status) as i32, + } + } +} + +impl prost::Message for MessageRefSlot { + fn encode_raw(&self, buf: &mut impl BufMut) { + let msg = self.0; + let status = CommitmentLevelProto::from(msg.status) as i32; + if msg.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); + } + if let ::core::option::Option::Some(ref value) = msg.parent { + ::prost::encoding::uint64::encode(2u32, value, buf); + } + if status != CommitmentLevelProto::default() as i32 { + ::prost::encoding::int32::encode(3u32, &status, buf); + } + } + + fn encoded_len(&self) -> usize { + let msg = self.0; + let status = CommitmentLevelProto::from(msg.status) as i32; + (if msg.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + } else { + 0 + }) + msg.parent.as_ref().map_or(0, |value| { + ::prost::encoding::uint64::encoded_len(2u32, value) + }) + if status != CommitmentLevelProto::default() as i32 { + ::prost::encoding::int32::encoded_len(3u32, &status) + } else { + 0 + } + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + #[derive(Debug)] pub struct MessageRefBlock { pub meta: Arc, @@ -434,8 +494,8 @@ pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { #[cfg(test)] mod tests { use { - super::{FilterName, Message, MessageEntry, MessageFilters, MessageRef}, - crate::geyser::SubscribeUpdate, + super::{FilterName, Message, MessageEntry, MessageFilters, MessageRef, MessageSlot}, + crate::{geyser::SubscribeUpdate, plugin::message::CommitmentLevel}, prost::Message as _, std::sync::Arc, }; @@ -484,6 +544,28 @@ mod tests { assert_eq!(update, SubscribeUpdate::from(&msg)); } + #[test] + fn test_message_slot() { + for slot in [0, 42] { + for parent in [None, Some(0), Some(42)] { + for status in [ + CommitmentLevel::Processed, + CommitmentLevel::Confirmed, + CommitmentLevel::Finalized, + ] { + encode_decode_cmp( + &["123"], + MessageRef::slot(MessageSlot { + slot, + parent, + status, + }), + ) + } + } + } + } + #[test] fn test_message_ping() { encode_decode_cmp(&["123"], MessageRef::Ping) From 2d2139a809ddde12ee7367e556fe2dd5afc9d6bd Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 14:28:10 +0200 Subject: [PATCH 20/50] rm wrap, replace predefined block --- yellowstone-grpc-geyser/src/filters.rs | 4 +- yellowstone-grpc-proto/Cargo.toml | 2 - .../src/plugin/blocks/43200.bincode | Bin 1496 -> 0 bytes .../src/plugin/blocks/64800004.bincode | Bin 0 -> 657404 bytes .../src/plugin/message_ref.rs | 276 ++++++++++-------- 5 files changed, 156 insertions(+), 126 deletions(-) delete mode 100644 yellowstone-grpc-proto/src/plugin/blocks/43200.bincode create mode 100644 yellowstone-grpc-proto/src/plugin/blocks/64800004.bincode diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 2a837e64..2220fbe0 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -797,7 +797,7 @@ impl FilterEntries { fn get_filters(&self, message: &Arc) -> FilteredMessages { let filters = self.filters.as_slice(); - filtered_messages_once_ref!(filters, FilteredMessageRef::entry(message)) + filtered_messages_once_ref!(filters, FilteredMessageRef::entry(Arc::clone(message))) } } @@ -961,7 +961,7 @@ impl FilterBlocksMeta { fn get_filters(&self, message: &Arc) -> FilteredMessages { let filters = self.filters.as_slice(); - filtered_messages_once_ref!(filters, FilteredMessageRef::block_meta(message)) + filtered_messages_once_ref!(filters, FilteredMessageRef::block_meta(Arc::clone(message))) } } diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index 85508561..6eeddb7a 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -39,8 +39,6 @@ plugin = [ "dep:agave-geyser-plugin-interface", "dep:bytes", "dep:smallvec", - "dep:solana-sdk", - "dep:solana-transaction-status", "dep:thiserror" ] tonic-compression = ["tonic/gzip", "tonic/zstd"] diff --git a/yellowstone-grpc-proto/src/plugin/blocks/43200.bincode b/yellowstone-grpc-proto/src/plugin/blocks/43200.bincode deleted file mode 100644 index 2b5c16fec76613d0d995b73a2255f128d2774938..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1496 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

VHn4=|HZY@Ado_6K-JksBd;Zh# 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%0Dd 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�HmYQJ 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

xWaPP@6di{yh>nj}P@o0k3GQ%IADn2V9j8 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

AR)V^ddtT=G&Y@!VI*22PPC2+nQ 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 literal 0 HcmV?d00001 diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 95637263..b6d228ba 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -23,6 +23,7 @@ use { DecodeError, }, smallvec::SmallVec, + solana_transaction_status::Reward, std::{ops::Range, sync::Arc}, }; @@ -92,15 +93,15 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; #[derive(Debug)] pub enum MessageRef { - Account, // 2 - Slot(MessageRefSlot), // 3 - Transaction, // 4 - TransactionStatus, // 10 - Block, // 5 - Ping, // 6 - Pong(MessageRefPong), // 9 - BlockMeta(MessageRefBlockMeta), // 7 - Entry(MessageRefEntry), // 8 + Account, // 2 + Slot(MessageSlot), // 3 + Transaction, // 4 + TransactionStatus, // 10 + Block, // 5 + Ping, // 6 + Pong(MessageRefPong), // 9 + BlockMeta(Arc), // 7 + Entry(Arc), // 8 } impl From<&MessageRef> for UpdateOneof { @@ -113,8 +114,8 @@ impl From<&MessageRef> for UpdateOneof { MessageRef::Block => todo!(), MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), MessageRef::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), - MessageRef::BlockMeta(msg) => Self::BlockMeta(msg.into()), - MessageRef::Entry(msg) => Self::Entry(msg.into()), + MessageRef::BlockMeta(msg) => Self::BlockMeta(msg.as_ref().into()), + MessageRef::Entry(msg) => Self::Entry(msg.as_ref().into()), } } } @@ -132,8 +133,8 @@ impl prost::Message for MessageRef { encode_varint(0, buf); } MessageRef::Pong(msg) => message::encode(9u32, msg, buf), - MessageRef::BlockMeta(msg) => message::encode(7u32, msg, buf), - MessageRef::Entry(msg) => message::encode(8u32, msg, buf), + MessageRef::BlockMeta(msg) => message::encode(7u32, msg.as_ref(), buf), + MessageRef::Entry(msg) => message::encode(8u32, msg.as_ref(), buf), } } @@ -146,8 +147,8 @@ impl prost::Message for MessageRef { MessageRef::Block => todo!(), MessageRef::Ping => 0, MessageRef::Pong(msg) => message::encoded_len(9u32, msg), - MessageRef::BlockMeta(msg) => message::encoded_len(7u32, msg), - MessageRef::Entry(msg) => message::encoded_len(8u32, msg), + MessageRef::BlockMeta(msg) => message::encoded_len(7u32, msg.as_ref()), + MessageRef::Entry(msg) => message::encoded_len(8u32, msg.as_ref()), } } @@ -172,7 +173,7 @@ impl MessageRef { } pub const fn slot(message: MessageSlot) -> Self { - Self::Slot(MessageRefSlot(message)) + Self::Slot(message) } pub fn transaction(message: &MessageTransaction) -> Self { @@ -191,20 +192,17 @@ impl MessageRef { Self::Pong(MessageRefPong { id }) } - pub fn block_meta(message: &Arc) -> Self { - Self::BlockMeta(MessageRefBlockMeta(Arc::clone(message))) + pub fn block_meta(message: Arc) -> Self { + Self::BlockMeta(message) } - pub fn entry(message: &Arc) -> Self { - Self::Entry(MessageRefEntry(Arc::clone(message))) + pub fn entry(message: Arc) -> Self { + Self::Entry(message) } } -#[derive(Debug)] -pub struct MessageRefSlot(pub MessageSlot); - -impl From<&MessageRefSlot> for SubscribeUpdateSlot { - fn from(MessageRefSlot(msg): &MessageRefSlot) -> Self { +impl From<&MessageSlot> for SubscribeUpdateSlot { + fn from(msg: &MessageSlot) -> Self { Self { slot: msg.slot, parent: msg.parent, @@ -213,14 +211,13 @@ impl From<&MessageRefSlot> for SubscribeUpdateSlot { } } -impl prost::Message for MessageRefSlot { +impl prost::Message for MessageSlot { fn encode_raw(&self, buf: &mut impl BufMut) { - let msg = self.0; - let status = CommitmentLevelProto::from(msg.status) as i32; - if msg.slot != 0u64 { - ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); + let status = CommitmentLevelProto::from(self.status) as i32; + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &self.slot, buf); } - if let ::core::option::Option::Some(ref value) = msg.parent { + if let ::core::option::Option::Some(ref value) = self.parent { ::prost::encoding::uint64::encode(2u32, value, buf); } if status != CommitmentLevelProto::default() as i32 { @@ -229,13 +226,12 @@ impl prost::Message for MessageRefSlot { } fn encoded_len(&self) -> usize { - let msg = self.0; - let status = CommitmentLevelProto::from(msg.status) as i32; - (if msg.slot != 0u64 { - ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + let status = CommitmentLevelProto::from(self.status) as i32; + (if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { 0 - }) + msg.parent.as_ref().map_or(0, |value| { + }) + self.parent.as_ref().map_or(0, |value| { ::prost::encoding::uint64::encoded_len(2u32, value) }) + if status != CommitmentLevelProto::default() as i32 { ::prost::encoding::int32::encoded_len(3u32, &status) @@ -275,11 +271,8 @@ pub struct MessageRefPong { pub id: i32, } -#[derive(Debug)] -pub struct MessageRefBlockMeta(pub Arc); - -impl From<&MessageRefBlockMeta> for SubscribeUpdateBlockMeta { - fn from(MessageRefBlockMeta(msg): &MessageRefBlockMeta) -> Self { +impl From<&MessageBlockMeta> for SubscribeUpdateBlockMeta { + fn from(msg: &MessageBlockMeta) -> Self { Self { slot: msg.slot, blockhash: msg.blockhash.clone(), @@ -297,9 +290,8 @@ impl From<&MessageRefBlockMeta> for SubscribeUpdateBlockMeta { } } -impl prost::Message for MessageRefBlockMeta { +impl prost::Message for MessageBlockMeta { fn encode_raw(&self, buf: &mut impl BufMut) { - // let msg = &self.0; // if self.slot != 0u64 { // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); // } @@ -335,48 +327,50 @@ impl prost::Message for MessageRefBlockMeta { } fn encoded_len(&self) -> usize { - // let msg = &self.0; - // (if msg.slot != 0u64 { - // ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) - // } else { - // 0 - // }) + if msg.blockhash != "" { - // ::prost::encoding::string::encoded_len(2u32, &msg.blockhash) - // } else { - // 0 - // } + self - // .rewards - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(3u32, msg)) - // + self - // .block_time - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) - // + self - // .block_height - // .as_ref() - // .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) - // + if msg.parent_slot != 0u64 { - // ::prost::encoding::uint64::encoded_len(6u32, &msg.parent_slot) - // } else { - // 0 - // } - // + if msg.parent_blockhash != "" { - // ::prost::encoding::string::encoded_len(7u32, &msg.parent_blockhash) - // } else { - // 0 - // } - // + if msg.executed_transaction_count != 0u64 { - // ::prost::encoding::uint64::encoded_len(8u32, &msg.executed_transaction_count) - // } else { - // 0 - // } - // + if msg.entries_count != 0u64 { - // ::prost::encoding::uint64::encoded_len(9u32, &msg.entries_count) - // } else { - // 0 - // } - todo!() + let block_time = self.block_time.map(convert_to::create_timestamp); + let block_height = self.block_height.map(convert_to::create_block_height); + (if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &self.slot) + } else { + 0 + }) + if self.blockhash != "" { + ::prost::encoding::string::encoded_len(2u32, &self.blockhash) + } else { + 0 + } + { + let len = key_len(1u32) * self.rewards.len() + + self.rewards.iter().map(|reward| 0).sum::() + + self + .num_partitions + .as_ref() + .map_or(0, |msg| ::prost::encoding::message::encoded_len(2u32, msg)); + key_len(3u32) + encoded_len_varint(len as u64) + len + } + block_time + .as_ref() + .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) + + block_height + .as_ref() + .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) + + if self.parent_slot != 0u64 { + ::prost::encoding::uint64::encoded_len(6u32, &self.parent_slot) + } else { + 0 + } + + if self.parent_blockhash != "" { + ::prost::encoding::string::encoded_len(7u32, &self.parent_blockhash) + } else { + 0 + } + + if self.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(8u32, &self.executed_transaction_count) + } else { + 0 + } + + if self.entries_count != 0u64 { + ::prost::encoding::uint64::encoded_len(9u32, &self.entries_count) + } else { + 0 + } } fn merge_field( @@ -394,11 +388,8 @@ impl prost::Message for MessageRefBlockMeta { } } -#[derive(Debug)] -pub struct MessageRefEntry(pub Arc); - -impl From<&MessageRefEntry> for SubscribeUpdateEntry { - fn from(MessageRefEntry(msg): &MessageRefEntry) -> Self { +impl From<&MessageEntry> for SubscribeUpdateEntry { + fn from(msg: &MessageEntry) -> Self { Self { slot: msg.slot, index: msg.index as u64, @@ -410,55 +401,53 @@ impl From<&MessageRefEntry> for SubscribeUpdateEntry { } } -impl prost::Message for MessageRefEntry { +impl prost::Message for MessageEntry { fn encode_raw(&self, buf: &mut impl BufMut) { - let msg = &self.0; - let index = msg.index as u64; - if msg.slot != 0u64 { - ::prost::encoding::uint64::encode(1u32, &msg.slot, buf); + let index = self.index as u64; + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &self.slot, buf); } if index != 0u64 { ::prost::encoding::uint64::encode(2u32, &index, buf); } - if msg.num_hashes != 0u64 { - ::prost::encoding::uint64::encode(3u32, &msg.num_hashes, buf); + if self.num_hashes != 0u64 { + ::prost::encoding::uint64::encode(3u32, &self.num_hashes, buf); } - if !msg.hash.is_empty() { - prost_bytes_encode_raw(4u32, &msg.hash, buf); + if !self.hash.is_empty() { + prost_bytes_encode_raw(4u32, &self.hash, buf); } - if msg.executed_transaction_count != 0u64 { - ::prost::encoding::uint64::encode(5u32, &msg.executed_transaction_count, buf); + if self.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encode(5u32, &self.executed_transaction_count, buf); } - if msg.starting_transaction_index != 0u64 { - ::prost::encoding::uint64::encode(6u32, &msg.starting_transaction_index, buf); + if self.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encode(6u32, &self.starting_transaction_index, buf); } } fn encoded_len(&self) -> usize { - let msg = &self.0; - let index = msg.index as u64; - (if msg.slot != 0u64 { - ::prost::encoding::uint64::encoded_len(1u32, &msg.slot) + let index = self.index as u64; + (if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { 0 }) + if index != 0u64 { ::prost::encoding::uint64::encoded_len(2u32, &index) } else { 0 - } + if msg.num_hashes != 0u64 { - ::prost::encoding::uint64::encoded_len(3u32, &msg.num_hashes) + } + if self.num_hashes != 0u64 { + ::prost::encoding::uint64::encoded_len(3u32, &self.num_hashes) } else { 0 - } + if !msg.hash.is_empty() { - prost_bytes_encoded_len(4u32, &msg.hash) + } + if !self.hash.is_empty() { + prost_bytes_encoded_len(4u32, &self.hash) } else { 0 - } + if msg.executed_transaction_count != 0u64 { - ::prost::encoding::uint64::encoded_len(5u32, &msg.executed_transaction_count) + } + if self.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &self.executed_transaction_count) } else { 0 - } + if msg.starting_transaction_index != 0u64 { - ::prost::encoding::uint64::encoded_len(6u32, &msg.starting_transaction_index) + } + if self.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encoded_len(6u32, &self.starting_transaction_index) } else { 0 } @@ -494,10 +483,16 @@ pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { #[cfg(test)] mod tests { use { - super::{FilterName, Message, MessageEntry, MessageFilters, MessageRef, MessageSlot}, + super::{ + FilterName, Message, MessageBlockMeta, MessageEntry, MessageFilters, MessageRef, + MessageSlot, + }, crate::{geyser::SubscribeUpdate, plugin::message::CommitmentLevel}, prost::Message as _, - std::sync::Arc, + prost_011::Message as _, + solana_storage_proto::convert::generated, + solana_transaction_status::ConfirmedBlock, + std::{fs, sync::Arc}, }; fn create_message_filters(names: &[&str]) -> MessageFilters { @@ -580,21 +575,58 @@ mod tests { #[test] fn test_message_entry() { for entry in create_entries() { - encode_decode_cmp(&["123"], MessageRef::entry(&entry)); + encode_decode_cmp(&["123"], MessageRef::entry(entry)); } } #[test] fn test_predefined() { - use {prost_011::Message as _, solana_storage_proto::convert::generated, std::fs}; - let location = "./src/plugin/blocks"; for entry in fs::read_dir(location).expect("failed to read `blocks` dir") { let path = entry.expect("failed to read `blocks` dir entry").path(); let data = fs::read(path).expect("failed to read block"); - let block = - generated::ConfirmedBlock::decode(data.as_slice()).expect("failed to decode block"); + let block: ConfirmedBlock = generated::ConfirmedBlock::decode(data.as_slice()) + .expect("failed to decode block") + .try_into() + .expect("failed to convert decoded block"); + + let block_meta = Arc::new(MessageBlockMeta { + parent_slot: block.parent_slot, + slot: block.parent_slot + 1, + parent_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + rewards: block.rewards, + num_partitions: block.num_partitions, + block_time: block.block_time, + block_height: block.block_height, + executed_transaction_count: block.transactions.len() as u64, + entries_count: create_entries().len() as u64, + }); + // encode_decode_cmp(&["123"], MessageRef::block_meta(block_meta)); + let msg = Message { + filters: create_message_filters(&["123"]), + message: MessageRef::block_meta(block_meta), + }; + println!( + "my {} vs proto {}", + msg.encoded_len(), + SubscribeUpdate::from(&msg).encoded_len() + ); } + + // use prost::{ + // encoding::{encoded_len_varint, key_len}, + // Message as _, + // }; + // let msg = crate::solana::storage::confirmed_block::BlockHeight { block_height: 42 }; + // let s1 = ::prost::encoding::message::encoded_len(5u32, &msg); + // println!("{:?}", s1); + // println!( + // "{} {} {}", + // key_len(5u32), + // encoded_len_varint(msg.encoded_len() as u64), + // msg.encoded_len() + // ); } } From 54b405251aecea0e11bf74619d840246361ae3d3 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 15:56:45 +0200 Subject: [PATCH 21/50] block meta enc --- yellowstone-grpc-proto/src/lib.rs | 25 +- .../src/plugin/message_ref.rs | 230 ++++++++++++------ 2 files changed, 174 insertions(+), 81 deletions(-) diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 4561bb37..66b76942 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -233,8 +233,7 @@ pub mod convert_to { pub fn create_rewards_obj(rewards: &[Reward], num_partitions: Option) -> proto::Rewards { proto::Rewards { rewards: create_rewards(rewards), - num_partitions: num_partitions - .map(|num_partitions| proto::NumPartitions { num_partitions }), + num_partitions: num_partitions.map(create_num_partitions), } } @@ -247,17 +246,25 @@ pub mod convert_to { 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, + reward_type: create_reward_type(reward.reward_type) as i32, commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(), } } + pub const fn create_reward_type(reward_type: Option) -> proto::RewardType { + match 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, + } + } + + pub const fn create_num_partitions(num_partitions: u64) -> proto::NumPartitions { + proto::NumPartitions { num_partitions } + } + pub fn create_return_data(return_data: &TransactionReturnData) -> proto::ReturnData { proto::ReturnData { program_id: return_data.program_id.to_bytes().into(), diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index b6d228ba..60f33c26 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -13,6 +13,7 @@ use { SubscribeUpdate, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, SubscribeUpdateSlot, }, + solana::storage::confirmed_block::RewardType as RewardTypeProto, }, bytes::buf::{Buf, BufMut}, prost::{ @@ -292,71 +293,61 @@ impl From<&MessageBlockMeta> for SubscribeUpdateBlockMeta { impl prost::Message for MessageBlockMeta { fn encode_raw(&self, buf: &mut impl BufMut) { - // if self.slot != 0u64 { - // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); - // } - // if self.blockhash != "" { - // ::prost::encoding::string::encode(2u32, &self.blockhash, buf); - // } - // if let Some(ref msg) = self.rewards { - // ::prost::encoding::message::encode(3u32, msg, buf); - // } - // if let Some(ref msg) = self.block_time { - // ::prost::encoding::message::encode(4u32, msg, buf); - // } - // if let Some(ref msg) = self.block_height { - // ::prost::encoding::message::encode(5u32, msg, buf); - // } - // if self.parent_slot != 0u64 { - // ::prost::encoding::uint64::encode(6u32, &self.parent_slot, buf); - // } - // if self.parent_blockhash != "" { - // ::prost::encoding::string::encode(7u32, &self.parent_blockhash, buf); - // } - // if self.executed_transaction_count != 0u64 { - // ::prost::encoding::uint64::encode( - // 8u32, - // &self.executed_transaction_count, - // buf, - // ); - // } - // if self.entries_count != 0u64 { - // ::prost::encoding::uint64::encode(9u32, &self.entries_count, buf); - // } - todo!() + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &self.slot, buf); + } + if !self.blockhash.is_empty() { + ::prost::encoding::string::encode(2u32, &self.blockhash, buf); + } + self.rewards_encode(buf); + if let Some(block_time) = self.block_time { + let msg = convert_to::create_timestamp(block_time); + ::prost::encoding::message::encode(4u32, &msg, buf); + } + if let Some(block_height) = self.block_height { + let msg = convert_to::create_block_height(block_height); + ::prost::encoding::message::encode(5u32, &msg, buf); + } + if self.parent_slot != 0u64 { + ::prost::encoding::uint64::encode(6u32, &self.parent_slot, buf); + } + if !self.parent_blockhash.is_empty() { + ::prost::encoding::string::encode(7u32, &self.parent_blockhash, buf); + } + if self.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encode(8u32, &self.executed_transaction_count, buf); + } + if self.entries_count != 0u64 { + ::prost::encoding::uint64::encode(9u32, &self.entries_count, buf); + } } fn encoded_len(&self) -> usize { - let block_time = self.block_time.map(convert_to::create_timestamp); - let block_height = self.block_height.map(convert_to::create_block_height); (if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { 0 - }) + if self.blockhash != "" { + }) + if !self.blockhash.is_empty() { ::prost::encoding::string::encoded_len(2u32, &self.blockhash) } else { 0 } + { - let len = key_len(1u32) * self.rewards.len() - + self.rewards.iter().map(|reward| 0).sum::() - + self - .num_partitions - .as_ref() - .map_or(0, |msg| ::prost::encoding::message::encoded_len(2u32, msg)); + let len = self.rewards_encoded_len(); key_len(3u32) + encoded_len_varint(len as u64) + len - } + block_time - .as_ref() - .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, msg)) - + block_height - .as_ref() - .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, msg)) + } + self + .block_time + .map(convert_to::create_timestamp) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, &msg)) + + self + .block_height + .map(convert_to::create_block_height) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) + if self.parent_slot != 0u64 { ::prost::encoding::uint64::encoded_len(6u32, &self.parent_slot) } else { 0 } - + if self.parent_blockhash != "" { + + if !self.parent_blockhash.is_empty() { ::prost::encoding::string::encoded_len(7u32, &self.parent_blockhash) } else { 0 @@ -388,6 +379,117 @@ impl prost::Message for MessageBlockMeta { } } +impl MessageBlockMeta { + fn rewards_encode(&self, buf: &mut impl BufMut) { + encode_key(3u32, WireType::LengthDelimited, buf); + encode_varint(self.rewards_encoded_len() as u64, buf); + for reward in &self.rewards { + Self::reward_encode(reward, buf); + } + if let Some(num_partitions) = self.num_partitions { + let msg = convert_to::create_num_partitions(num_partitions); + ::prost::encoding::message::encode(2u32, &msg, buf); + } + } + + fn reward_encode(reward: &Reward, buf: &mut impl BufMut) { + encode_key(1u32, WireType::LengthDelimited, buf); + encode_varint(Self::reward_encoded_len(reward) as u64, buf); + + let reward_type = convert_to::create_reward_type(reward.reward_type) as i32; + let commission = Self::commission_to_str(reward.commission); + + if !reward.pubkey.is_empty() { + ::prost::encoding::string::encode(1u32, &reward.pubkey, buf); + } + if reward.lamports != 0i64 { + ::prost::encoding::int64::encode(2u32, &reward.lamports, buf); + } + if reward.post_balance != 0u64 { + ::prost::encoding::uint64::encode(3u32, &reward.post_balance, buf); + } + if reward_type != RewardTypeProto::default() as i32 { + ::prost::encoding::int32::encode(4u32, &reward_type, buf); + } + if commission != b"" { + prost_bytes_encode_raw(5u32, commission, buf); + } + } + + fn rewards_encoded_len(&self) -> usize { + key_len(1u32) * self.rewards.len() + + self + .rewards + .iter() + .map(Self::reward_encoded_len) + .map(|len| len + encoded_len_varint(len as u64)) + .sum::() + + self + .num_partitions + .map(convert_to::create_num_partitions) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(2u32, &msg)) + } + + fn reward_encoded_len(reward: &Reward) -> usize { + let reward_type = convert_to::create_reward_type(reward.reward_type) as i32; + let commission = Self::commission_to_str(reward.commission); + (if !reward.pubkey.is_empty() { + ::prost::encoding::string::encoded_len(1u32, &reward.pubkey) + } else { + 0 + }) + if reward.lamports != 0i64 { + ::prost::encoding::int64::encoded_len(2u32, &reward.lamports) + } else { + 0 + } + if reward.post_balance != 0u64 { + ::prost::encoding::uint64::encoded_len(3u32, &reward.post_balance) + } else { + 0 + } + if reward_type != RewardTypeProto::default() as i32 { + ::prost::encoding::int32::encoded_len(4u32, &reward_type) + } else { + 0 + } + if commission != b"" { + prost_bytes_encoded_len(5u32, commission) + } else { + 0 + } + } + + const fn commission_to_str(commission: Option) -> &'static [u8] { + const TABLE: [&[u8]; 256] = [ + b"0", b"1", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9", b"10", b"11", b"12", b"13", + b"14", b"15", b"16", b"17", b"18", b"19", b"20", b"21", b"22", b"23", b"24", b"25", + b"26", b"27", b"28", b"29", b"30", b"31", b"32", b"33", b"34", b"35", b"36", b"37", + b"38", b"39", b"40", b"41", b"42", b"43", b"44", b"45", b"46", b"47", b"48", b"49", + b"50", b"51", b"52", b"53", b"54", b"55", b"56", b"57", b"58", b"59", b"60", b"61", + b"62", b"63", b"64", b"65", b"66", b"67", b"68", b"69", b"70", b"71", b"72", b"73", + b"74", b"75", b"76", b"77", b"78", b"79", b"80", b"81", b"82", b"83", b"84", b"85", + b"86", b"87", b"88", b"89", b"90", b"91", b"92", b"93", b"94", b"95", b"96", b"97", + b"98", b"99", b"100", b"101", b"102", b"103", b"104", b"105", b"106", b"107", b"108", + b"109", b"110", b"111", b"112", b"113", b"114", b"115", b"116", b"117", b"118", b"119", + b"120", b"121", b"122", b"123", b"124", b"125", b"126", b"127", b"128", b"129", b"130", + b"131", b"132", b"133", b"134", b"135", b"136", b"137", b"138", b"139", b"140", b"141", + b"142", b"143", b"144", b"145", b"146", b"147", b"148", b"149", b"150", b"151", b"152", + b"153", b"154", b"155", b"156", b"157", b"158", b"159", b"160", b"161", b"162", b"163", + b"164", b"165", b"166", b"167", b"168", b"169", b"170", b"171", b"172", b"173", b"174", + b"175", b"176", b"177", b"178", b"179", b"180", b"181", b"182", b"183", b"184", b"185", + b"186", b"187", b"188", b"189", b"190", b"191", b"192", b"193", b"194", b"195", b"196", + b"197", b"198", b"199", b"200", b"201", b"202", b"203", b"204", b"205", b"206", b"207", + b"208", b"209", b"210", b"211", b"212", b"213", b"214", b"215", b"216", b"217", b"218", + b"219", b"220", b"221", b"222", b"223", b"224", b"225", b"226", b"227", b"228", b"229", + b"230", b"231", b"232", b"233", b"234", b"235", b"236", b"237", b"238", b"239", b"240", + b"241", b"242", b"243", b"244", b"245", b"246", b"247", b"248", b"249", b"250", b"251", + b"252", b"253", b"254", b"255", + ]; + if let Some(index) = commission { + TABLE[index as usize] + } else { + &[] + } + } +} + impl From<&MessageEntry> for SubscribeUpdateEntry { fn from(msg: &MessageEntry) -> Self { Self { @@ -590,7 +692,7 @@ mod tests { .try_into() .expect("failed to convert decoded block"); - let block_meta = Arc::new(MessageBlockMeta { + let mut block_meta = MessageBlockMeta { parent_slot: block.parent_slot, slot: block.parent_slot + 1, parent_blockhash: block.previous_blockhash, @@ -601,32 +703,16 @@ mod tests { block_height: block.block_height, executed_transaction_count: block.transactions.len() as u64, entries_count: create_entries().len() as u64, - }); - // encode_decode_cmp(&["123"], MessageRef::block_meta(block_meta)); - let msg = Message { - filters: create_message_filters(&["123"]), - message: MessageRef::block_meta(block_meta), }; - println!( - "my {} vs proto {}", - msg.encoded_len(), - SubscribeUpdate::from(&msg).encoded_len() + + encode_decode_cmp( + &["123"], + MessageRef::block_meta(Arc::new(block_meta.clone())), ); - } - // use prost::{ - // encoding::{encoded_len_varint, key_len}, - // Message as _, - // }; - // let msg = crate::solana::storage::confirmed_block::BlockHeight { block_height: 42 }; - // let s1 = ::prost::encoding::message::encoded_len(5u32, &msg); - // println!("{:?}", s1); - // println!( - // "{} {} {}", - // key_len(5u32), - // encoded_len_varint(msg.encoded_len() as u64), - // msg.encoded_len() - // ); + block_meta.num_partitions = Some(42); + encode_decode_cmp(&["123"], MessageRef::block_meta(Arc::new(block_meta))); + } } } From 381d56aa1253faeefe31f2f43d8183ec836df865 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 18:52:52 +0200 Subject: [PATCH 22/50] add data_slice limit to config --- yellowstone-grpc-geyser/config.json | 3 +- yellowstone-grpc-geyser/src/config.rs | 2 + yellowstone-grpc-geyser/src/filters.rs | 67 ++++++++++--------- yellowstone-grpc-proto/src/plugin/filter.rs | 16 +++-- .../src/plugin/message_ref.rs | 4 +- 5 files changed, 53 insertions(+), 39 deletions(-) diff --git a/yellowstone-grpc-geyser/config.json b/yellowstone-grpc-geyser/config.json index e9004630..921ad261 100644 --- a/yellowstone-grpc-geyser/config.json +++ b/yellowstone-grpc-geyser/config.json @@ -38,7 +38,8 @@ "owner_max": 10, "owner_reject": [ "11111111111111111111111111111111" - ] + ], + "data_slice_max": 2 }, "slots": { "max": 1 diff --git a/yellowstone-grpc-geyser/src/config.rs b/yellowstone-grpc-geyser/src/config.rs index 9774c205..a84d5d59 100644 --- a/yellowstone-grpc-geyser/src/config.rs +++ b/yellowstone-grpc-geyser/src/config.rs @@ -273,6 +273,7 @@ pub struct ConfigGrpcFiltersAccounts { pub owner_max: usize, #[serde(deserialize_with = "deserialize_pubkey_set")] pub owner_reject: HashSet, + pub data_slice_max: usize, } impl Default for ConfigGrpcFiltersAccounts { @@ -284,6 +285,7 @@ impl Default for ConfigGrpcFiltersAccounts { account_reject: HashSet::new(), owner_max: usize::MAX, owner_reject: HashSet::new(), + data_slice_max: usize::MAX, } } } diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 2220fbe0..651d8d68 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -16,7 +16,7 @@ use { }, yellowstone_grpc_proto::{ plugin::{ - filter::{FilterName, FilterNames}, + filter::{FilterAccountsDataSlice, FilterName, FilterNames}, message::{ CommitmentLevel, Message, MessageAccount, MessageBlock, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, @@ -74,7 +74,7 @@ pub struct Filter { blocks: FilterBlocks, blocks_meta: FilterBlocksMeta, commitment: CommitmentLevel, - accounts_data_slice: Vec>, + accounts_data_slice: FilterAccountsDataSlice, ping: Option, } @@ -95,7 +95,7 @@ impl Default for Filter { blocks: FilterBlocks::default(), blocks_meta: FilterBlocksMeta::default(), commitment: CommitmentLevel::Processed, - accounts_data_slice: vec![], + accounts_data_slice: FilterAccountsDataSlice::default(), ping: None, } } @@ -126,7 +126,10 @@ impl Filter { blocks: FilterBlocks::new(&config.blocks, &limit.blocks, names)?, blocks_meta: FilterBlocksMeta::new(&config.blocks_meta, &limit.blocks_meta, names)?, commitment: Self::decode_commitment(config.commitment)?, - accounts_data_slice: FilterAccountsDataSlice::create(&config.accounts_data_slice)?, + accounts_data_slice: parse_accounts_data_slice_create( + &config.accounts_data_slice, + limit.accounts.data_slice_max, + )?, ping: config.ping.as_ref().map(|msg| msg.id), }) } @@ -308,7 +311,7 @@ impl FilterAccounts { fn get_filters( &self, message: &MessageAccount, - accounts_data_slice: &[Range], + accounts_data_slice: &FilterAccountsDataSlice, ) -> FilteredMessages { let mut filter = FilterAccountsMatch::new(self); filter.match_txn_signature(&message.account.txn_signature); @@ -318,7 +321,7 @@ impl FilterAccounts { let filters = filter.get_filters(); filtered_messages_once_owned!( filters, - FilteredMessageRef::account(message, accounts_data_slice.to_vec()) + FilteredMessageRef::account(message.clone(), accounts_data_slice.clone()) ) } } @@ -965,35 +968,37 @@ impl FilterBlocksMeta { } } -#[derive(Debug, Clone, Copy)] -pub struct FilterAccountsDataSlice; - -impl FilterAccountsDataSlice { - pub fn create( - slices: &[SubscribeRequestAccountsDataSlice], - ) -> anyhow::Result>> { - let slices = slices - .iter() - .map(|s| Range { - start: s.offset as usize, - end: (s.offset + s.length) as usize, - }) - .collect::>(); - - for (i, slice_a) in slices.iter().enumerate() { - // check order - for slice_b in slices[i + 1..].iter() { - anyhow::ensure!(slice_a.start <= slice_b.start, "data slices out of order"); - } +pub fn parse_accounts_data_slice_create( + slices: &[SubscribeRequestAccountsDataSlice], + limit: usize, +) -> anyhow::Result { + anyhow::ensure!( + slices.len() <= limit, + "Max amount of data_slices reached, only {} allowed", + limit + ); + + let slices = slices + .iter() + .map(|s| Range { + start: s.offset as usize, + end: (s.offset + s.length) as usize, + }) + .collect::(); - // check overlap - for slice_b in slices[0..i].iter() { - anyhow::ensure!(slice_a.start >= slice_b.end, "data slices overlap"); - } + for (i, slice_a) in slices.iter().enumerate() { + // check order + for slice_b in slices[i + 1..].iter() { + anyhow::ensure!(slice_a.start <= slice_b.start, "data slices out of order"); } - Ok(slices) + // check overlap + for slice_b in slices[0..i].iter() { + anyhow::ensure!(slice_a.start >= slice_b.end, "data slices overlap"); + } } + + Ok(slices) } #[cfg(test)] diff --git a/yellowstone-grpc-proto/src/plugin/filter.rs b/yellowstone-grpc-proto/src/plugin/filter.rs index 97033ddf..90450942 100644 --- a/yellowstone-grpc-proto/src/plugin/filter.rs +++ b/yellowstone-grpc-proto/src/plugin/filter.rs @@ -1,8 +1,12 @@ -use std::{ - borrow::Borrow, - collections::HashSet, - sync::Arc, - time::{Duration, Instant}, +use { + smallvec::SmallVec, + std::{ + borrow::Borrow, + collections::HashSet, + ops::Range, + sync::Arc, + time::{Duration, Instant}, + }, }; #[derive(Debug, thiserror::Error)] @@ -91,3 +95,5 @@ impl FilterNames { } } } + +pub type FilterAccountsDataSlice = SmallVec<[Range; 4]>; diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 60f33c26..08e5bc1e 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -1,6 +1,6 @@ use { super::{ - filter::FilterName, + filter::{FilterAccountsDataSlice, FilterName}, message::{ MessageAccount, MessageAccountInfo, MessageBlockMeta, MessageEntry, MessageSlot, MessageTransaction, MessageTransactionInfo, @@ -169,7 +169,7 @@ impl prost::Message for MessageRef { } impl MessageRef { - pub fn account(message: &MessageAccount, accounts_data_slice: Vec>) -> Self { + pub fn account(message: MessageAccount, accounts_data_slice: FilterAccountsDataSlice) -> Self { todo!() } From 1363c77c212c3774f59e3993904f276fd9c0d33c Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 19:16:10 +0200 Subject: [PATCH 23/50] prepare account --- .../src/plugin/message_ref.rs | 105 ++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 08e5bc1e..1fbad376 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -10,7 +10,8 @@ use { convert_to, geyser::{ subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, - SubscribeUpdate, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, + SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, + SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, SubscribeUpdatePong, SubscribeUpdateSlot, }, solana::storage::confirmed_block::RewardType as RewardTypeProto, @@ -94,7 +95,7 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; #[derive(Debug)] pub enum MessageRef { - Account, // 2 + Account(MessageAccountRef), // 2 Slot(MessageSlot), // 3 Transaction, // 4 TransactionStatus, // 10 @@ -108,7 +109,7 @@ pub enum MessageRef { impl From<&MessageRef> for UpdateOneof { fn from(message: &MessageRef) -> Self { match message { - MessageRef::Account => todo!(), + MessageRef::Account(msg) => Self::Account(msg.into()), MessageRef::Slot(msg) => Self::Slot(msg.into()), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), @@ -124,7 +125,7 @@ impl From<&MessageRef> for UpdateOneof { impl prost::Message for MessageRef { fn encode_raw(&self, buf: &mut impl BufMut) { match self { - MessageRef::Account => todo!(), + MessageRef::Account(msg) => message::encode(2u32, msg, buf), MessageRef::Slot(msg) => message::encode(3u32, msg, buf), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), @@ -141,7 +142,7 @@ impl prost::Message for MessageRef { fn encoded_len(&self) -> usize { match self { - MessageRef::Account => todo!(), + MessageRef::Account(msg) => message::encoded_len(2u32, msg), MessageRef::Slot(msg) => message::encoded_len(3u32, msg), MessageRef::Transaction => todo!(), MessageRef::TransactionStatus => todo!(), @@ -169,8 +170,13 @@ impl prost::Message for MessageRef { } impl MessageRef { - pub fn account(message: MessageAccount, accounts_data_slice: FilterAccountsDataSlice) -> Self { - todo!() + pub fn account(message: MessageAccount, data_slice: FilterAccountsDataSlice) -> Self { + Self::Account(MessageAccountRef { + slot: message.slot, + account: message.account, + is_startup: message.is_startup, + data_slice, + }) } pub const fn slot(message: MessageSlot) -> Self { @@ -202,6 +208,91 @@ impl MessageRef { } } +#[derive(Debug)] +pub struct MessageAccountRef { + pub account: Arc, + pub slot: u64, + pub is_startup: bool, + pub data_slice: FilterAccountsDataSlice, +} + +impl From<&MessageAccountRef> for SubscribeUpdateAccount { + fn from(msg: &MessageAccountRef) -> Self { + let data = if msg.data_slice.is_empty() { + msg.account.data.clone() + } else { + let mut data = Vec::with_capacity(msg.data_slice.iter().map(|s| s.end - s.start).sum()); + for slice in msg.data_slice.iter() { + if msg.account.data.len() >= slice.end { + data.extend_from_slice(&msg.account.data[slice.start..slice.end]); + } + } + data + }; + + SubscribeUpdateAccount { + account: Some(SubscribeUpdateAccountInfo { + pubkey: msg.account.pubkey.as_ref().into(), + lamports: msg.account.lamports, + owner: msg.account.owner.as_ref().into(), + executable: msg.account.executable, + rent_epoch: msg.account.rent_epoch, + data, + write_version: msg.account.write_version, + txn_signature: msg.account.txn_signature.map(|s| s.as_ref().into()), + }), + slot: msg.slot, + is_startup: msg.is_startup, + } + } +} + +impl prost::Message for MessageAccountRef { + fn encode_raw(&self, buf: &mut impl BufMut) { + // let status = CommitmentLevelProto::from(self.status) as i32; + // if self.slot != 0u64 { + // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); + // } + // if let ::core::option::Option::Some(ref value) = self.parent { + // ::prost::encoding::uint64::encode(2u32, value, buf); + // } + // if status != CommitmentLevelProto::default() as i32 { + // ::prost::encoding::int32::encode(3u32, &status, buf); + // } + todo!() + } + + fn encoded_len(&self) -> usize { + // let status = CommitmentLevelProto::from(self.status) as i32; + // (if self.slot != 0u64 { + // ::prost::encoding::uint64::encoded_len(1u32, &self.slot) + // } else { + // 0 + // }) + self.parent.as_ref().map_or(0, |value| { + // ::prost::encoding::uint64::encoded_len(2u32, value) + // }) + if status != CommitmentLevelProto::default() as i32 { + // ::prost::encoding::int32::encoded_len(3u32, &status) + // } else { + // 0 + // } + todo!() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + impl From<&MessageSlot> for SubscribeUpdateSlot { fn from(msg: &MessageSlot) -> Self { Self { From 09c2cb6a174114678352370400a3a80678ab22fd Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 21:43:25 +0200 Subject: [PATCH 24/50] impl account --- .../src/plugin/message_ref.rs | 293 ++++++++++++++---- 1 file changed, 226 insertions(+), 67 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 1fbad376..a03376fa 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -26,7 +26,7 @@ use { }, smallvec::SmallVec, solana_transaction_status::Reward, - std::{ops::Range, sync::Arc}, + std::{borrow::Cow, ops::Range, sync::Arc}, }; #[derive(Debug)] @@ -218,18 +218,6 @@ pub struct MessageAccountRef { impl From<&MessageAccountRef> for SubscribeUpdateAccount { fn from(msg: &MessageAccountRef) -> Self { - let data = if msg.data_slice.is_empty() { - msg.account.data.clone() - } else { - let mut data = Vec::with_capacity(msg.data_slice.iter().map(|s| s.end - s.start).sum()); - for slice in msg.data_slice.iter() { - if msg.account.data.len() >= slice.end { - data.extend_from_slice(&msg.account.data[slice.start..slice.end]); - } - } - data - }; - SubscribeUpdateAccount { account: Some(SubscribeUpdateAccountInfo { pubkey: msg.account.pubkey.as_ref().into(), @@ -237,7 +225,8 @@ impl From<&MessageAccountRef> for SubscribeUpdateAccount { owner: msg.account.owner.as_ref().into(), executable: msg.account.executable, rent_epoch: msg.account.rent_epoch, - data, + data: MessageAccountRef::accout_data_slice(&msg.account, &msg.data_slice) + .into_owned(), write_version: msg.account.write_version, txn_signature: msg.account.txn_signature.map(|s| s.as_ref().into()), }), @@ -249,33 +238,30 @@ impl From<&MessageAccountRef> for SubscribeUpdateAccount { impl prost::Message for MessageAccountRef { fn encode_raw(&self, buf: &mut impl BufMut) { - // let status = CommitmentLevelProto::from(self.status) as i32; - // if self.slot != 0u64 { - // ::prost::encoding::uint64::encode(1u32, &self.slot, buf); - // } - // if let ::core::option::Option::Some(ref value) = self.parent { - // ::prost::encoding::uint64::encode(2u32, value, buf); - // } - // if status != CommitmentLevelProto::default() as i32 { - // ::prost::encoding::int32::encode(3u32, &status, buf); - // } - todo!() + Self::account_encode(&self.account, &self.data_slice, 1u32, buf); + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(2u32, &self.slot, buf); + } + if self.is_startup { + ::prost::encoding::bool::encode(3u32, &self.is_startup, buf); + } } fn encoded_len(&self) -> usize { - // let status = CommitmentLevelProto::from(self.status) as i32; - // (if self.slot != 0u64 { - // ::prost::encoding::uint64::encoded_len(1u32, &self.slot) - // } else { - // 0 - // }) + self.parent.as_ref().map_or(0, |value| { - // ::prost::encoding::uint64::encoded_len(2u32, value) - // }) + if status != CommitmentLevelProto::default() as i32 { - // ::prost::encoding::int32::encoded_len(3u32, &status) - // } else { - // 0 - // } - todo!() + let len = Self::account_encoded_len(self.account.as_ref(), &self.data_slice); + key_len(1u32) + + encoded_len_varint(len as u64) + + len + + if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &self.slot) + } else { + 0 + } + + if self.is_startup { + ::prost::encoding::bool::encoded_len(3u32, &self.is_startup) + } else { + 0 + } } fn merge_field( @@ -293,6 +279,110 @@ impl prost::Message for MessageAccountRef { } } +impl MessageAccountRef { + fn accout_data_slice<'a>( + account: &'a MessageAccountInfo, + data_slice: &'a FilterAccountsDataSlice, + ) -> Cow<'a, Vec> { + if data_slice.is_empty() { + Cow::Borrowed(&account.data) + } else { + let mut data = Vec::with_capacity(data_slice.iter().map(|s| s.end - s.start).sum()); + for slice in data_slice.iter() { + if account.data.len() >= slice.end { + data.extend_from_slice(&account.data[slice.start..slice.end]); + } + } + Cow::Owned(data) + } + } + + fn account_data_slice_len( + account: &MessageAccountInfo, + data_slice: &FilterAccountsDataSlice, + ) -> usize { + if data_slice.is_empty() { + account.data.len() + } else { + let mut len = 0; + for slice in data_slice.iter() { + if account.data.len() >= slice.end { + len += account.data[slice.start..slice.end].len(); + } + } + len + } + } + + fn account_encode( + account: &MessageAccountInfo, + data_slice: &FilterAccountsDataSlice, + tag: u32, + buf: &mut impl BufMut, + ) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::account_encoded_len(account, data_slice) as u64, buf); + prost_bytes_encode_raw(1u32, account.pubkey.as_ref(), buf); + if account.lamports != 0u64 { + ::prost::encoding::uint64::encode(2u32, &account.lamports, buf); + } + prost_bytes_encode_raw(3u32, account.owner.as_ref(), buf); + if account.executable { + ::prost::encoding::bool::encode(4u32, &account.executable, buf); + } + if account.rent_epoch != 0u64 { + ::prost::encoding::uint64::encode(5u32, &account.rent_epoch, buf); + } + let data = Self::accout_data_slice(account, data_slice); + if !data.is_empty() { + prost_bytes_encode_raw(6u32, data.as_ref(), buf); + } + if account.write_version != 0u64 { + ::prost::encoding::uint64::encode(7u32, &account.write_version, buf); + } + if let Some(value) = &account.txn_signature { + prost_bytes_encode_raw(8u32, value.as_ref(), buf); + } + } + + fn account_encoded_len( + account: &MessageAccountInfo, + data_slice: &FilterAccountsDataSlice, + ) -> usize { + let data_len = Self::account_data_slice_len(account, data_slice); + prost_bytes_encoded_len(1u32, account.pubkey.as_ref()) + + if account.lamports != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &account.lamports) + } else { + 0 + } + + prost_bytes_encoded_len(3u32, account.owner.as_ref()) + + if account.executable { + ::prost::encoding::bool::encoded_len(4u32, &account.executable) + } else { + 0 + } + + if account.rent_epoch != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &account.rent_epoch) + } else { + 0 + } + + if data_len > 0 { + key_len(6u32) + encoded_len_varint(data_len as u64) + data_len + } else { + 0 + } + + if account.write_version != 0u64 { + ::prost::encoding::uint64::encoded_len(7u32, &account.write_version) + } else { + 0 + } + + account + .txn_signature + .map_or(0, |s| prost_bytes_encoded_len(8u32, s.as_ref())) + } +} + impl From<&MessageSlot> for SubscribeUpdateSlot { fn from(msg: &MessageSlot) -> Self { Self { @@ -390,7 +480,7 @@ impl prost::Message for MessageBlockMeta { if !self.blockhash.is_empty() { ::prost::encoding::string::encode(2u32, &self.blockhash, buf); } - self.rewards_encode(buf); + self.rewards_encode(3u32, buf); if let Some(block_time) = self.block_time { let msg = convert_to::create_timestamp(block_time); ::prost::encoding::message::encode(4u32, &msg, buf); @@ -471,8 +561,8 @@ impl prost::Message for MessageBlockMeta { } impl MessageBlockMeta { - fn rewards_encode(&self, buf: &mut impl BufMut) { - encode_key(3u32, WireType::LengthDelimited, buf); + fn rewards_encode(&self, tag: u32, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); encode_varint(self.rewards_encoded_len() as u64, buf); for reward in &self.rewards { Self::reward_encode(reward, buf); @@ -487,9 +577,6 @@ impl MessageBlockMeta { encode_key(1u32, WireType::LengthDelimited, buf); encode_varint(Self::reward_encoded_len(reward) as u64, buf); - let reward_type = convert_to::create_reward_type(reward.reward_type) as i32; - let commission = Self::commission_to_str(reward.commission); - if !reward.pubkey.is_empty() { ::prost::encoding::string::encode(1u32, &reward.pubkey, buf); } @@ -499,9 +586,11 @@ impl MessageBlockMeta { if reward.post_balance != 0u64 { ::prost::encoding::uint64::encode(3u32, &reward.post_balance, buf); } + let reward_type = convert_to::create_reward_type(reward.reward_type) as i32; if reward_type != RewardTypeProto::default() as i32 { ::prost::encoding::int32::encode(4u32, &reward_type, buf); } + let commission = Self::commission_to_str(reward.commission); if commission != b"" { prost_bytes_encode_raw(5u32, commission, buf); } @@ -606,9 +695,7 @@ impl prost::Message for MessageEntry { if self.num_hashes != 0u64 { ::prost::encoding::uint64::encode(3u32, &self.num_hashes, buf); } - if !self.hash.is_empty() { - prost_bytes_encode_raw(4u32, &self.hash, buf); - } + prost_bytes_encode_raw(4u32, &self.hash, buf); if self.executed_transaction_count != 0u64 { ::prost::encoding::uint64::encode(5u32, &self.executed_transaction_count, buf); } @@ -631,19 +718,17 @@ impl prost::Message for MessageEntry { ::prost::encoding::uint64::encoded_len(3u32, &self.num_hashes) } else { 0 - } + if !self.hash.is_empty() { - prost_bytes_encoded_len(4u32, &self.hash) - } else { - 0 - } + if self.executed_transaction_count != 0u64 { - ::prost::encoding::uint64::encoded_len(5u32, &self.executed_transaction_count) - } else { - 0 - } + if self.starting_transaction_index != 0u64 { - ::prost::encoding::uint64::encoded_len(6u32, &self.starting_transaction_index) - } else { - 0 - } + } + prost_bytes_encoded_len(4u32, &self.hash) + + if self.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &self.executed_transaction_count) + } else { + 0 + } + + if self.starting_transaction_index != 0u64 { + ::prost::encoding::uint64::encoded_len(6u32, &self.starting_transaction_index) + } else { + 0 + } } fn merge_field( @@ -677,15 +762,17 @@ pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { mod tests { use { super::{ - FilterName, Message, MessageBlockMeta, MessageEntry, MessageFilters, MessageRef, - MessageSlot, + FilterAccountsDataSlice, FilterName, Message, MessageAccount, MessageAccountInfo, + MessageBlockMeta, MessageEntry, MessageFilters, MessageRef, MessageSlot, + SubscribeUpdate, }, - crate::{geyser::SubscribeUpdate, plugin::message::CommitmentLevel}, + crate::plugin::message::CommitmentLevel, prost::Message as _, prost_011::Message as _, + solana_sdk::{pubkey::Pubkey, signature::Signature}, solana_storage_proto::convert::generated, solana_transaction_status::ConfirmedBlock, - std::{fs, sync::Arc}, + std::{fs, ops::Range, str::FromStr, sync::Arc}, }; fn create_message_filters(names: &[&str]) -> MessageFilters { @@ -696,6 +783,72 @@ mod tests { filters } + fn create_accounts() -> Vec<(MessageAccount, FilterAccountsDataSlice)> { + let pubkey = Pubkey::from_str("28Dncoh8nmzXYEGLUcBA5SUw5WDwDBn15uUCwrWBbyuu").unwrap(); + let owner = Pubkey::from_str("5jrPJWVGrFvQ2V9wRZC3kHEZhxo9pmMir15x73oHT6mn").unwrap(); + let txn_signature = Signature::from_str("4V36qYhukXcLFuvhZaudSoJpPaFNB7d5RqYKjL2xiSKrxaBfEajqqL4X6viZkEvHJ8XcTJsqVjZxFegxhN7EC9V5").unwrap(); + + let mut accounts = vec![]; + for lamports in [0, 8123] { + for executable in [true, false] { + for rent_epoch in [0, 4242] { + for data in [vec![], vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] { + for write_version in [0, 1] { + for txn_signature in [None, Some(txn_signature)] { + accounts.push(Arc::new(MessageAccountInfo { + pubkey, + lamports, + owner, + executable, + rent_epoch, + data: data.clone(), + write_version, + txn_signature, + })); + } + } + } + } + } + } + // accounts + + let mut vec = vec![]; + for account in accounts { + for slot in [0, 42] { + for is_startup in [true, false] { + for data_slice in create_account_data_slice() { + let msg = MessageAccount { + account: Arc::clone(&account), + slot, + is_startup, + }; + vec.push((msg, data_slice)); + } + } + } + } + vec + } + + fn create_account_data_slice() -> Vec { + let mut data_slice1 = FilterAccountsDataSlice::new(); + data_slice1.push(Range { start: 0, end: 0 }); + + let mut data_slice2 = FilterAccountsDataSlice::new(); + data_slice2.push(Range { start: 2, end: 3 }); + + let mut data_slice3 = FilterAccountsDataSlice::new(); + data_slice3.push(Range { start: 1, end: 3 }); + + vec![ + FilterAccountsDataSlice::new(), + data_slice1, + data_slice2, + data_slice3, + ] + } + fn create_entries() -> Vec> { [ MessageEntry { @@ -725,13 +878,18 @@ mod tests { filters: create_message_filters(filters), message, }; - // println!("{:?}", SubscribeUpdate::from(&msg)); let bytes = msg.encode_to_vec(); let update = SubscribeUpdate::decode(bytes.as_slice()).expect("failed to decode"); - // println!("{update:?}"); assert_eq!(update, SubscribeUpdate::from(&msg)); } + #[test] + fn test_message_account() { + for (msg, data_slice) in create_accounts() { + encode_decode_cmp(&["123"], MessageRef::account(msg, data_slice)); + } + } + #[test] fn test_message_slot() { for slot in [0, 42] { @@ -783,9 +941,10 @@ mod tests { .try_into() .expect("failed to convert decoded block"); + let slot = block.parent_slot + 1; let mut block_meta = MessageBlockMeta { parent_slot: block.parent_slot, - slot: block.parent_slot + 1, + slot, parent_blockhash: block.previous_blockhash, blockhash: block.blockhash, rewards: block.rewards, From 70a0cf303b171857b8144fbd60ed7821927ce6ae Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 23:13:41 +0200 Subject: [PATCH 25/50] serialize tx status --- yellowstone-grpc-geyser/src/filters.rs | 8 +- yellowstone-grpc-proto/src/lib.rs | 20 +- .../src/plugin/message_ref.rs | 176 +++++++++++++++--- 3 files changed, 165 insertions(+), 39 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 651d8d68..e81a7d1d 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -770,7 +770,7 @@ impl FilterTransactions { match self.filter_type { FilterTransactionsType::Transaction => FilteredMessageRef::transaction(message), FilterTransactionsType::TransactionStatus => { - FilteredMessageRef::transaction_status(message) + FilteredMessageRef::transaction_status(message.clone()) } } ) @@ -1250,7 +1250,7 @@ mod tests { assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageRef::TransactionStatus + FilteredMessageRef::TransactionStatus(_) )); } @@ -1306,7 +1306,7 @@ mod tests { assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageRef::TransactionStatus + FilteredMessageRef::TransactionStatus(_) )); } @@ -1414,7 +1414,7 @@ mod tests { assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( updates[1].message, - FilteredMessageRef::TransactionStatus + FilteredMessageRef::TransactionStatus(_) )); } diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 66b76942..1418c5c2 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -39,7 +39,7 @@ pub mod convert_to { }, pubkey::Pubkey, signature::Signature, - transaction::SanitizedTransaction, + transaction::{SanitizedTransaction, TransactionError}, transaction_context::TransactionReturnData, }, solana_transaction_status::{ @@ -140,12 +140,7 @@ pub mod convert_to { 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 err = create_transaction_error(status); let inner_instructions_none = inner_instructions.is_none(); let inner_instructions = inner_instructions .as_deref() @@ -185,6 +180,17 @@ pub mod convert_to { } } + pub fn create_transaction_error( + status: &Result<(), TransactionError>, + ) -> Option { + match status { + Ok(()) => None, + Err(err) => Some(proto::TransactionError { + err: bincode::serialize(&err).expect("transaction error to serialize to bytes"), + }), + } + } + pub fn create_inner_instructions_vec( ixs: &[InnerInstructions], ) -> Vec { diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index a03376fa..faee929a 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -12,7 +12,7 @@ use { subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, - SubscribeUpdatePong, SubscribeUpdateSlot, + SubscribeUpdatePong, SubscribeUpdateSlot, SubscribeUpdateTransactionStatus, }, solana::storage::confirmed_block::RewardType as RewardTypeProto, }, @@ -64,7 +64,8 @@ impl prost::Message for Message { .filters .iter() .map(|filter| { - encoded_len_varint(filter.as_ref().len() as u64) + filter.as_ref().len() + let len = filter.as_ref().len(); + encoded_len_varint(len as u64) + len }) .sum::() + self.message.encoded_len() @@ -95,15 +96,15 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; #[derive(Debug)] pub enum MessageRef { - Account(MessageAccountRef), // 2 - Slot(MessageSlot), // 3 - Transaction, // 4 - TransactionStatus, // 10 - Block, // 5 - Ping, // 6 - Pong(MessageRefPong), // 9 - BlockMeta(Arc), // 7 - Entry(Arc), // 8 + Account(MessageAccountRef), // 2 + Slot(MessageSlot), // 3 + Transaction, // 4 + TransactionStatus(MessageTransactionStatusRef), // 10 + Block, // 5 + Ping, // 6 + Pong(MessageRefPong), // 9 + BlockMeta(Arc), // 7 + Entry(Arc), // 8 } impl From<&MessageRef> for UpdateOneof { @@ -112,7 +113,7 @@ impl From<&MessageRef> for UpdateOneof { MessageRef::Account(msg) => Self::Account(msg.into()), MessageRef::Slot(msg) => Self::Slot(msg.into()), MessageRef::Transaction => todo!(), - MessageRef::TransactionStatus => todo!(), + MessageRef::TransactionStatus(msg) => Self::TransactionStatus(msg.into()), MessageRef::Block => todo!(), MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), MessageRef::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), @@ -128,7 +129,7 @@ impl prost::Message for MessageRef { MessageRef::Account(msg) => message::encode(2u32, msg, buf), MessageRef::Slot(msg) => message::encode(3u32, msg, buf), MessageRef::Transaction => todo!(), - MessageRef::TransactionStatus => todo!(), + MessageRef::TransactionStatus(msg) => message::encode(10u32, msg, buf), MessageRef::Block => todo!(), MessageRef::Ping => { encode_key(6u32, WireType::LengthDelimited, buf); @@ -145,9 +146,9 @@ impl prost::Message for MessageRef { MessageRef::Account(msg) => message::encoded_len(2u32, msg), MessageRef::Slot(msg) => message::encoded_len(3u32, msg), MessageRef::Transaction => todo!(), - MessageRef::TransactionStatus => todo!(), + MessageRef::TransactionStatus(msg) => message::encoded_len(10u32, msg), MessageRef::Block => todo!(), - MessageRef::Ping => 0, + MessageRef::Ping => key_len(6u32) + encoded_len_varint(0), MessageRef::Pong(msg) => message::encoded_len(9u32, msg), MessageRef::BlockMeta(msg) => message::encoded_len(7u32, msg.as_ref()), MessageRef::Entry(msg) => message::encoded_len(8u32, msg.as_ref()), @@ -187,8 +188,11 @@ impl MessageRef { todo!() } - pub fn transaction_status(message: &MessageTransaction) -> Self { - todo!() + pub fn transaction_status(message: MessageTransaction) -> Self { + Self::TransactionStatus(MessageTransactionStatusRef { + transaction: message.transaction, + slot: message.slot, + }) } pub fn block(message: MessageRefBlock) -> Self { @@ -437,6 +441,83 @@ impl prost::Message for MessageSlot { } } +#[derive(Debug)] +pub struct MessageTransactionStatusRef { + pub transaction: Arc, + pub slot: u64, +} + +impl From<&MessageTransactionStatusRef> for SubscribeUpdateTransactionStatus { + fn from(msg: &MessageTransactionStatusRef) -> Self { + SubscribeUpdateTransactionStatus { + slot: msg.slot, + signature: msg.transaction.signature.as_ref().to_vec(), + is_vote: msg.transaction.is_vote, + index: msg.transaction.index as u64, + err: convert_to::create_transaction_error(&msg.transaction.meta.status), + } + } +} + +impl prost::Message for MessageTransactionStatusRef { + fn encode_raw(&self, buf: &mut impl BufMut) { + let tx = &self.transaction; + let index = tx.index as u64; + let err = convert_to::create_transaction_error(&tx.meta.status); + + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &self.slot, buf); + } + prost_bytes_encode_raw(2u32, tx.signature.as_ref(), buf); + if tx.is_vote { + ::prost::encoding::bool::encode(3u32, &tx.is_vote, buf); + } + if index != 0u64 { + ::prost::encoding::uint64::encode(4u32, &index, buf); + } + if let Some(msg) = err { + ::prost::encoding::message::encode(5u32, &msg, buf); + } + } + + fn encoded_len(&self) -> usize { + let tx = &self.transaction; + let index = tx.index as u64; + let err = convert_to::create_transaction_error(&tx.meta.status); + + (if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &self.slot) + } else { + 0 + }) + prost_bytes_encoded_len(2u32, tx.signature.as_ref()) + + if tx.is_vote { + ::prost::encoding::bool::encoded_len(3u32, &tx.is_vote) + } else { + 0 + } + + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(4u32, &index) + } else { + 0 + } + + err.map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + #[derive(Debug)] pub struct MessageRefBlock { pub meta: Arc, @@ -761,18 +842,19 @@ pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { #[cfg(test)] mod tests { use { - super::{ - FilterAccountsDataSlice, FilterName, Message, MessageAccount, MessageAccountInfo, - MessageBlockMeta, MessageEntry, MessageFilters, MessageRef, MessageSlot, - SubscribeUpdate, - }, + super::*, crate::plugin::message::CommitmentLevel, prost::Message as _, prost_011::Message as _, - solana_sdk::{pubkey::Pubkey, signature::Signature}, + solana_sdk::{ + message::SimpleAddressLoader, + pubkey::Pubkey, + signature::Signature, + transaction::{MessageHash, SanitizedTransaction}, + }, solana_storage_proto::convert::generated, - solana_transaction_status::ConfirmedBlock, - std::{fs, ops::Range, str::FromStr, sync::Arc}, + solana_transaction_status::{ConfirmedBlock, TransactionWithStatusMeta}, + std::{collections::HashSet, fs, str::FromStr}, }; fn create_message_filters(names: &[&str]) -> MessageFilters { @@ -878,9 +960,12 @@ mod tests { filters: create_message_filters(filters), message, }; - let bytes = msg.encode_to_vec(); - let update = SubscribeUpdate::decode(bytes.as_slice()).expect("failed to decode"); - assert_eq!(update, SubscribeUpdate::from(&msg)); + let update = SubscribeUpdate::from(&msg); + assert_eq!(msg.encoded_len(), update.encoded_len()); + assert_eq!( + SubscribeUpdate::decode(msg.encode_to_vec().as_slice()).expect("failed to decode"), + update + ); } #[test] @@ -962,6 +1047,41 @@ mod tests { block_meta.num_partitions = Some(42); encode_decode_cmp(&["123"], MessageRef::block_meta(Arc::new(block_meta))); + + let transactions_info = block + .transactions + .iter() + .enumerate() + .map(|(index, tx)| { + let TransactionWithStatusMeta::Complete(tx) = tx else { + panic!("tx with missed meta"); + }; + let transaction = SanitizedTransaction::try_create( + tx.transaction.clone(), + MessageHash::Compute, + None, + SimpleAddressLoader::Disabled, + &HashSet::new(), + ) + .expect("failed to create tx"); + MessageTransactionInfo { + signature: tx.transaction.signatures[0], + is_vote: true, + transaction, + meta: tx.meta.clone(), + index, + } + }) + .map(Arc::new) + .collect::>(); + + for tx in transactions_info.iter() { + let msg = MessageTransaction { + transaction: Arc::clone(tx), + slot: 42, + }; + encode_decode_cmp(&["123"], MessageRef::transaction_status(msg)); + } } } } From 338328c6316c14966187c376154b850d8cd86fdb Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 7 Nov 2024 23:36:29 +0200 Subject: [PATCH 26/50] pre tx --- yellowstone-grpc-geyser/src/filters.rs | 10 +-- .../src/plugin/message_ref.rs | 79 ++++++++++++++++--- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index e81a7d1d..4e8aaa7a 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -321,7 +321,7 @@ impl FilterAccounts { let filters = filter.get_filters(); filtered_messages_once_owned!( filters, - FilteredMessageRef::account(message.clone(), accounts_data_slice.clone()) + FilteredMessageRef::account(message, accounts_data_slice.clone()) ) } } @@ -770,7 +770,7 @@ impl FilterTransactions { match self.filter_type { FilterTransactionsType::Transaction => FilteredMessageRef::transaction(message), FilterTransactionsType::TransactionStatus => { - FilteredMessageRef::transaction_status(message.clone()) + FilteredMessageRef::transaction_status(message) } } ) @@ -1245,7 +1245,7 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageRef::Transaction + FilteredMessageRef::Transaction(_) )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( @@ -1301,7 +1301,7 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageRef::Transaction + FilteredMessageRef::Transaction(_) )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( @@ -1409,7 +1409,7 @@ mod tests { ); assert!(matches!( updates[0].message, - FilteredMessageRef::Transaction + FilteredMessageRef::Transaction(_) )); assert_eq!(updates[1].filters, FilteredMessageFilters::new()); assert!(matches!( diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index faee929a..75857bf0 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -12,7 +12,8 @@ use { subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, - SubscribeUpdatePong, SubscribeUpdateSlot, SubscribeUpdateTransactionStatus, + SubscribeUpdatePong, SubscribeUpdateSlot, SubscribeUpdateTransaction, + SubscribeUpdateTransactionInfo, SubscribeUpdateTransactionStatus, }, solana::storage::confirmed_block::RewardType as RewardTypeProto, }, @@ -98,7 +99,7 @@ pub type MessageFilters = SmallVec<[FilterName; 4]>; pub enum MessageRef { Account(MessageAccountRef), // 2 Slot(MessageSlot), // 3 - Transaction, // 4 + Transaction(MessageTransactionRef), // 4 TransactionStatus(MessageTransactionStatusRef), // 10 Block, // 5 Ping, // 6 @@ -112,7 +113,7 @@ impl From<&MessageRef> for UpdateOneof { match message { MessageRef::Account(msg) => Self::Account(msg.into()), MessageRef::Slot(msg) => Self::Slot(msg.into()), - MessageRef::Transaction => todo!(), + MessageRef::Transaction(msg) => Self::Transaction(msg.into()), MessageRef::TransactionStatus(msg) => Self::TransactionStatus(msg.into()), MessageRef::Block => todo!(), MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), @@ -128,7 +129,7 @@ impl prost::Message for MessageRef { match self { MessageRef::Account(msg) => message::encode(2u32, msg, buf), MessageRef::Slot(msg) => message::encode(3u32, msg, buf), - MessageRef::Transaction => todo!(), + MessageRef::Transaction(msg) => message::encode(4u32, msg, buf), MessageRef::TransactionStatus(msg) => message::encode(10u32, msg, buf), MessageRef::Block => todo!(), MessageRef::Ping => { @@ -145,7 +146,7 @@ impl prost::Message for MessageRef { match self { MessageRef::Account(msg) => message::encoded_len(2u32, msg), MessageRef::Slot(msg) => message::encoded_len(3u32, msg), - MessageRef::Transaction => todo!(), + MessageRef::Transaction(msg) => message::encoded_len(4u32, msg), MessageRef::TransactionStatus(msg) => message::encoded_len(10u32, msg), MessageRef::Block => todo!(), MessageRef::Ping => key_len(6u32) + encoded_len_varint(0), @@ -171,10 +172,10 @@ impl prost::Message for MessageRef { } impl MessageRef { - pub fn account(message: MessageAccount, data_slice: FilterAccountsDataSlice) -> Self { + pub fn account(message: &MessageAccount, data_slice: FilterAccountsDataSlice) -> Self { Self::Account(MessageAccountRef { slot: message.slot, - account: message.account, + account: Arc::clone(&message.account), is_startup: message.is_startup, data_slice, }) @@ -185,12 +186,15 @@ impl MessageRef { } pub fn transaction(message: &MessageTransaction) -> Self { - todo!() + Self::Transaction(MessageTransactionRef { + transaction: Arc::clone(&message.transaction), + slot: message.slot, + }) } - pub fn transaction_status(message: MessageTransaction) -> Self { + pub fn transaction_status(message: &MessageTransaction) -> Self { Self::TransactionStatus(MessageTransactionStatusRef { - transaction: message.transaction, + transaction: Arc::clone(&message.transaction), slot: message.slot, }) } @@ -441,6 +445,55 @@ impl prost::Message for MessageSlot { } } +#[derive(Debug)] +pub struct MessageTransactionRef { + pub transaction: Arc, + pub slot: u64, +} + +impl From<&MessageTransactionRef> for SubscribeUpdateTransaction { + fn from(msg: &MessageTransactionRef) -> Self { + Self { + transaction: Some(SubscribeUpdateTransactionInfo { + signature: msg.transaction.signature.as_ref().into(), + is_vote: msg.transaction.is_vote, + transaction: Some(convert_to::create_transaction(&msg.transaction.transaction)), + meta: Some(convert_to::create_transaction_meta(&msg.transaction.meta)), + index: msg.transaction.index as u64, + }), + slot: msg.slot, + } + } +} + +impl prost::Message for MessageTransactionRef { + fn encode_raw(&self, buf: &mut impl BufMut) { + todo!() + } + + fn encoded_len(&self) -> usize { + todo!() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + +impl MessageTransactionRef { + // +} + #[derive(Debug)] pub struct MessageTransactionStatusRef { pub transaction: Arc, @@ -449,7 +502,7 @@ pub struct MessageTransactionStatusRef { impl From<&MessageTransactionStatusRef> for SubscribeUpdateTransactionStatus { fn from(msg: &MessageTransactionStatusRef) -> Self { - SubscribeUpdateTransactionStatus { + Self { slot: msg.slot, signature: msg.transaction.signature.as_ref().to_vec(), is_vote: msg.transaction.is_vote, @@ -971,7 +1024,7 @@ mod tests { #[test] fn test_message_account() { for (msg, data_slice) in create_accounts() { - encode_decode_cmp(&["123"], MessageRef::account(msg, data_slice)); + encode_decode_cmp(&["123"], MessageRef::account(&msg, data_slice)); } } @@ -1080,7 +1133,7 @@ mod tests { transaction: Arc::clone(tx), slot: 42, }; - encode_decode_cmp(&["123"], MessageRef::transaction_status(msg)); + encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); } } } From da9a4a8964b776ba18a3a975c5f35539db8fd12d Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 09:36:33 +0200 Subject: [PATCH 27/50] save --- .../src/plugin/message_ref.rs | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 75857bf0..624c6b35 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -26,7 +26,8 @@ use { DecodeError, }, smallvec::SmallVec, - solana_transaction_status::Reward, + solana_sdk::transaction::SanitizedTransaction, + solana_transaction_status::{Reward, TransactionStatusMeta}, std::{borrow::Cow, ops::Range, sync::Arc}, }; @@ -472,7 +473,15 @@ impl prost::Message for MessageTransactionRef { } fn encoded_len(&self) -> usize { - todo!() + let len = Self::tx_meta_encoded_len(&self.transaction); + key_len(1u32) + + encoded_len_varint(len as u64) + + len + + if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &self.slot) + } else { + 0 + } } fn merge_field( @@ -491,7 +500,36 @@ impl prost::Message for MessageTransactionRef { } impl MessageTransactionRef { - // + fn tx_meta_encoded_len(tx: &MessageTransactionInfo) -> usize { + let index = tx.index as u64; + prost_bytes_encoded_len(1u32, tx.signature.as_ref()) + + if tx.is_vote { + ::prost::encoding::bool::encoded_len(2u32, &tx.is_vote) + } else { + 0 + } + + { + let len = Self::tx_encoded_len(&tx.transaction); + key_len(3u32) + encoded_len_varint(len as u64) + len + } + + { + let len = Self::meta_encoded_len(&tx.meta); + key_len(4u32) + encoded_len_varint(len as u64) + len + } + + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(5u32, &index) + } else { + 0 + } + } + + fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { + 0 + } + + fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { + 0 + } } #[derive(Debug)] @@ -514,20 +552,19 @@ impl From<&MessageTransactionStatusRef> for SubscribeUpdateTransactionStatus { impl prost::Message for MessageTransactionStatusRef { fn encode_raw(&self, buf: &mut impl BufMut) { - let tx = &self.transaction; - let index = tx.index as u64; - let err = convert_to::create_transaction_error(&tx.meta.status); - if self.slot != 0u64 { ::prost::encoding::uint64::encode(1u32, &self.slot, buf); } + let tx = &self.transaction; prost_bytes_encode_raw(2u32, tx.signature.as_ref(), buf); if tx.is_vote { ::prost::encoding::bool::encode(3u32, &tx.is_vote, buf); } + let index = tx.index as u64; if index != 0u64 { ::prost::encoding::uint64::encode(4u32, &index, buf); } + let err = convert_to::create_transaction_error(&tx.meta.status); if let Some(msg) = err { ::prost::encoding::message::encode(5u32, &msg, buf); } @@ -535,9 +572,6 @@ impl prost::Message for MessageTransactionStatusRef { fn encoded_len(&self) -> usize { let tx = &self.transaction; - let index = tx.index as u64; - let err = convert_to::create_transaction_error(&tx.meta.status); - (if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { @@ -548,12 +582,16 @@ impl prost::Message for MessageTransactionStatusRef { } else { 0 } - + if index != 0u64 { - ::prost::encoding::uint64::encoded_len(4u32, &index) - } else { - 0 + + { + let index = tx.index as u64; + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(4u32, &index) + } else { + 0 + } } - + err.map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) + + convert_to::create_transaction_error(&tx.meta.status) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) } fn merge_field( @@ -946,7 +984,6 @@ mod tests { } } } - // accounts let mut vec = vec![]; for account in accounts { @@ -1133,6 +1170,7 @@ mod tests { transaction: Arc::clone(tx), slot: 42, }; + encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); } } From e84ec516da3f74676b70ce504f9a7421f81f08b1 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 14:36:53 +0200 Subject: [PATCH 28/50] add bench --- Cargo.lock | 205 ++++++++++++++++++ Cargo.toml | 1 + yellowstone-grpc-proto/Cargo.toml | 9 + yellowstone-grpc-proto/benches/encode.rs | 57 +++++ .../src/plugin/message_ref.rs | 110 +++++++--- 5 files changed, 357 insertions(+), 25 deletions(-) create mode 100644 yellowstone-grpc-proto/benches/encode.rs diff --git a/Cargo.lock b/Cargo.lock index 6c4e6cdb..cbde6336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,6 +126,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.15" @@ -717,6 +723,12 @@ dependencies = [ "url", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.1.8" @@ -754,6 +766,33 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -883,6 +922,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -892,6 +967,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -1453,6 +1547,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1510,6 +1614,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1825,6 +1935,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -2199,6 +2320,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2318,6 +2445,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2642,6 +2797,26 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -2864,6 +3039,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -3836,6 +4020,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -4237,6 +4431,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4652,6 +4856,7 @@ dependencies = [ "anyhow", "bincode", "bytes", + "criterion", "prost 0.11.9", "prost 0.13.1", "protobuf-src", diff --git a/Cargo.toml b/Cargo.toml index 8ad29f67..26b7c4bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ bytes = "1.3.0" cargo-lock = "9.0.0" chrono = "0.4.26" clap = "4.3.0" +criterion = "0.5.1" crossbeam-channel = "0.5.8" env_logger = "0.11.3" futures = "0.3.24" diff --git a/yellowstone-grpc-proto/Cargo.toml b/yellowstone-grpc-proto/Cargo.toml index 6eeddb7a..2673fe26 100644 --- a/yellowstone-grpc-proto/Cargo.toml +++ b/yellowstone-grpc-proto/Cargo.toml @@ -10,19 +10,27 @@ license = "Apache-2.0" keywords = { workspace = true } publish = true +[[bench]] +name = "encode" +harness = false +required-features = ["plugin-bench"] + [dependencies] agave-geyser-plugin-interface = { workspace = true, optional = true } bincode = { workspace = true, optional = true } bytes = { workspace = true, optional = true } prost = { workspace = true } +prost_011 = { workspace = true, optional = true } smallvec = { workspace = true, optional = true } solana-account-decoder = { workspace = true, optional = true } solana-sdk = { workspace = true, optional = true } +solana-storage-proto = { workspace = true, optional = true } solana-transaction-status = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } tonic = { workspace = true } [dev-dependencies] +criterion = { workspace = true } prost_011 = { workspace = true } solana-storage-proto = { workspace = true } @@ -41,6 +49,7 @@ plugin = [ "dep:smallvec", "dep:thiserror" ] +plugin-bench = ["plugin", "dep:prost_011", "dep:solana-storage-proto"] tonic-compression = ["tonic/gzip", "tonic/zstd"] [lints] diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs new file mode 100644 index 00000000..2cfbada7 --- /dev/null +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -0,0 +1,57 @@ +use { + criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}, + prost::Message as _, + std::time::Duration, + yellowstone_grpc_proto::plugin::message_ref::{ + tests::{ + build_subscribe_update, build_subscribe_update_account, create_accounts, + create_message_filters, + }, + Message, MessageRef, + }, +}; + +fn bench_account(c: &mut Criterion) { + let accounts = create_accounts(); + let filters = create_message_filters(&["my special filter"]); + + c.bench_with_input( + BenchmarkId::new("accounts", "weak"), + &(&accounts, &filters), + |b, (accounts, filters)| { + b.iter(|| { + for (account, data_slice) in accounts.iter() { + let msg = Message { + filters: (*filters).clone(), + message: MessageRef::account(account, data_slice.clone()), + }; + msg.encode_to_vec().len(); + } + }) + }, + ); + c.bench_with_input( + BenchmarkId::new("accounts", "prost"), + &(&accounts, &filters), + |b, (accounts, filters)| { + b.iter(|| { + for (account, data_slice) in accounts.iter() { + let msg = build_subscribe_update( + filters, + build_subscribe_update_account(account, data_slice), + ); + msg.encode_to_vec().len(); + } + }) + }, + ); +} + +criterion_group!( + name = benches; + config = Criterion::default() + .warm_up_time(Duration::from_secs(3)) // default 3 + .measurement_time(Duration::from_secs(5)); // default 5 + targets = bench_account +); +criterion_main!(benches); diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 624c6b35..4d8c46c2 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -523,11 +523,11 @@ impl MessageTransactionRef { } } - fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { + const fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { 0 } - fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { + const fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { 0 } } @@ -930,8 +930,10 @@ pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "plugin-bench"))] +pub mod tests { + #![cfg_attr(feature = "plugin-bench", allow(dead_code))] + #![cfg_attr(feature = "plugin-bench", allow(unused_imports))] use { super::*, crate::plugin::message::CommitmentLevel, @@ -948,7 +950,7 @@ mod tests { std::{collections::HashSet, fs, str::FromStr}, }; - fn create_message_filters(names: &[&str]) -> MessageFilters { + pub fn create_message_filters(names: &[&str]) -> MessageFilters { let mut filters = MessageFilters::new(); for name in names { filters.push(FilterName::new(*name)); @@ -956,7 +958,26 @@ mod tests { filters } - fn create_accounts() -> Vec<(MessageAccount, FilterAccountsDataSlice)> { + fn create_account_data_slice() -> Vec { + let mut data_slice1 = FilterAccountsDataSlice::new(); + data_slice1.push(Range { start: 0, end: 0 }); + + let mut data_slice2 = FilterAccountsDataSlice::new(); + data_slice2.push(Range { start: 2, end: 3 }); + + let mut data_slice3 = FilterAccountsDataSlice::new(); + data_slice3.push(Range { start: 1, end: 3 }); + data_slice3.push(Range { start: 5, end: 10 }); + + vec![ + FilterAccountsDataSlice::new(), + data_slice1, + data_slice2, + data_slice3, + ] + } + + pub fn create_accounts() -> Vec<(MessageAccount, FilterAccountsDataSlice)> { let pubkey = Pubkey::from_str("28Dncoh8nmzXYEGLUcBA5SUw5WDwDBn15uUCwrWBbyuu").unwrap(); let owner = Pubkey::from_str("5jrPJWVGrFvQ2V9wRZC3kHEZhxo9pmMir15x73oHT6mn").unwrap(); let txn_signature = Signature::from_str("4V36qYhukXcLFuvhZaudSoJpPaFNB7d5RqYKjL2xiSKrxaBfEajqqL4X6viZkEvHJ8XcTJsqVjZxFegxhN7EC9V5").unwrap(); @@ -965,7 +986,12 @@ mod tests { for lamports in [0, 8123] { for executable in [true, false] { for rent_epoch in [0, 4242] { - for data in [vec![], vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] { + for data in [ + vec![], + [42; 165].to_vec(), + [42; 1024].to_vec(), + [42; 2 * 1024 * 1024].to_vec(), + ] { for write_version in [0, 1] { for txn_signature in [None, Some(txn_signature)] { accounts.push(Arc::new(MessageAccountInfo { @@ -1003,24 +1029,6 @@ mod tests { vec } - fn create_account_data_slice() -> Vec { - let mut data_slice1 = FilterAccountsDataSlice::new(); - data_slice1.push(Range { start: 0, end: 0 }); - - let mut data_slice2 = FilterAccountsDataSlice::new(); - data_slice2.push(Range { start: 2, end: 3 }); - - let mut data_slice3 = FilterAccountsDataSlice::new(); - data_slice3.push(Range { start: 1, end: 3 }); - - vec![ - FilterAccountsDataSlice::new(), - data_slice1, - data_slice2, - data_slice3, - ] - } - fn create_entries() -> Vec> { [ MessageEntry { @@ -1175,6 +1183,58 @@ mod tests { } } } + + // benches + pub fn build_subscribe_update( + filters: &MessageFilters, + update: UpdateOneof, + ) -> SubscribeUpdate { + SubscribeUpdate { + filters: filters.iter().map(|f| f.as_ref().to_owned()).collect(), + update_oneof: Some(update), + } + } + + pub fn build_subscribe_update_account_proto( + message: &MessageAccountInfo, + data_slice: &FilterAccountsDataSlice, + ) -> SubscribeUpdateAccountInfo { + let data = if data_slice.is_empty() { + message.data.clone() + } else { + let mut data = Vec::with_capacity(data_slice.iter().map(|s| s.end - s.start).sum()); + for slice in data_slice { + if message.data.len() >= slice.end { + data.extend_from_slice(&message.data[slice.start..slice.end]); + } + } + data + }; + SubscribeUpdateAccountInfo { + pubkey: message.pubkey.as_ref().into(), + lamports: message.lamports, + owner: message.owner.as_ref().into(), + executable: message.executable, + rent_epoch: message.rent_epoch, + data, + write_version: message.write_version, + txn_signature: message.txn_signature.map(|s| s.as_ref().into()), + } + } + + pub fn build_subscribe_update_account( + message: &MessageAccount, + data_slice: &FilterAccountsDataSlice, + ) -> UpdateOneof { + UpdateOneof::Account(SubscribeUpdateAccount { + account: Some(build_subscribe_update_account_proto( + &message.account, + data_slice, + )), + slot: message.slot, + is_startup: message.is_startup, + }) + } } // #[derive(Debug, Clone)] From 5fd4ee58e93a74a6e8c45c59d4505603b1edcecf Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 19:32:58 +0200 Subject: [PATCH 29/50] draft tx encoded_len --- yellowstone-grpc-proto/benches/encode.rs | 2 +- yellowstone-grpc-proto/src/lib.rs | 6 +- .../src/plugin/message_ref.rs | 351 ++++++++++++++++-- 3 files changed, 329 insertions(+), 30 deletions(-) diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index 2cfbada7..aece074d 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -16,7 +16,7 @@ fn bench_account(c: &mut Criterion) { let filters = create_message_filters(&["my special filter"]); c.bench_with_input( - BenchmarkId::new("accounts", "weak"), + BenchmarkId::new("accounts", "ref"), &(&accounts, &filters), |b, (accounts, filters)| { b.iter(|| { diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index 1418c5c2..b7c14974 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -65,11 +65,7 @@ pub mod convert_to { header: Some(create_header(&message.header)), account_keys: create_pubkeys(&message.account_keys), recent_blockhash: message.recent_blockhash.to_bytes().into(), - instructions: message - .instructions - .iter() - .map(create_instruction) - .collect(), + instructions: create_instructions(&message.instructions), versioned: false, address_table_lookups: vec![], }, diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 4d8c46c2..d20ab8ed 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -26,11 +26,45 @@ use { DecodeError, }, smallvec::SmallVec, - solana_sdk::transaction::SanitizedTransaction, - solana_transaction_status::{Reward, TransactionStatusMeta}, + solana_account_decoder::parse_token::UiTokenAmount, + solana_sdk::{ + instruction::CompiledInstruction, + message::{ + v0::{LoadedMessage, MessageAddressTableLookup}, + LegacyMessage, MessageHeader, SanitizedMessage, + }, + transaction::SanitizedTransaction, + transaction_context::TransactionReturnData, + }, + solana_transaction_status::{ + InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta, TransactionTokenBalance, + }, std::{borrow::Cow, ops::Range, sync::Arc}, }; +#[inline] +fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(value.len() as u64, buf); + buf.put(value); +} + +#[inline] +pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { + key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() +} + +macro_rules! prost_message_repeated_encoded_len { + ($tag:expr, $values:expr, $get_len:expr) => {{ + key_len($tag) * $values.len() + + $values + .iter() + .map($get_len) + .map(|len| encoded_len_varint(len as u64) + len) + .sum::() + }}; +} + #[derive(Debug)] pub struct Message { pub filters: MessageFilters, @@ -65,10 +99,8 @@ impl prost::Message for Message { + self .filters .iter() - .map(|filter| { - let len = filter.as_ref().len(); - encoded_len_varint(len as u64) + len - }) + .map(|filter| filter.as_ref().len()) + .map(|len| encoded_len_varint(len as u64) + len) .sum::() + self.message.encoded_len() } @@ -523,12 +555,289 @@ impl MessageTransactionRef { } } - const fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { - 0 + fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { + let message_len = Self::message_encoded_len(tx.message()); + let signatures = tx.signatures(); + prost_message_repeated_encoded_len!(1u32, signatures, |sig| sig.as_ref().len()) + + key_len(2u32) + + encoded_len_varint(message_len as u64) + + message_len + } + + fn message_encoded_len(message: &SanitizedMessage) -> usize { + let (header, account_keys, recent_blockhash, cixs, versioned, atls) = match message { + SanitizedMessage::Legacy(LegacyMessage { message, .. }) => ( + message.header, + &message.account_keys, + &message.recent_blockhash, + &message.instructions, + true, + None, + ), + SanitizedMessage::V0(LoadedMessage { message, .. }) => ( + message.header, + &message.account_keys, + &message.recent_blockhash, + &message.instructions, + false, + Some(&message.address_table_lookups), + ), + }; + + let header_len = Self::header_encoded_len(header); + key_len(1u32) + + encoded_len_varint(header_len as u64) + + header_len + + key_len(2u32) * account_keys.len() + + account_keys + .iter() + .map(|account_key| account_key.as_ref().len()) + .map(|len| encoded_len_varint(len as u64) + len) + .sum::() + + prost_bytes_encoded_len(3u32, recent_blockhash.as_ref()) + + key_len(4u32) * cixs.len() + + cixs + .iter() + .map(Self::cix_encoded_len) + .map(|len| encoded_len_varint(len as u64) + len) + .sum::() + + if versioned { + ::prost::encoding::bool::encoded_len(5u32, &versioned) + } else { + 0 + } + + if let Some(atls) = atls { + key_len(6u32) * atls.len() + + atls + .iter() + .map(Self::atl_encoded_len) + .map(|len| encoded_len_varint(len as u64) + len) + .sum::() + } else { + 0 + } + } + + fn header_encoded_len(header: MessageHeader) -> usize { + let num_required_signatures = header.num_required_signatures as u32; + let num_readonly_signed_accounts = header.num_readonly_signed_accounts as u32; + let num_readonly_unsigned_accounts = header.num_readonly_unsigned_accounts as u32; + (if num_required_signatures != 0u32 { + ::prost::encoding::uint32::encoded_len(1u32, &num_required_signatures) + } else { + 0 + }) + if num_readonly_signed_accounts != 0u32 { + ::prost::encoding::uint32::encoded_len(2u32, &num_readonly_signed_accounts) + } else { + 0 + } + if num_readonly_unsigned_accounts != 0u32 { + ::prost::encoding::uint32::encoded_len(3u32, &num_readonly_unsigned_accounts) + } else { + 0 + } + } + + fn cix_encoded_len(cix: &CompiledInstruction) -> usize { + let program_id_index = cix.program_id_index as u32; + (if program_id_index != 0u32 { + ::prost::encoding::uint32::encoded_len(1u32, &program_id_index) + } else { + 0 + }) + if !cix.accounts.is_empty() { + prost_bytes_encoded_len(2u32, &cix.accounts) + } else { + 0 + } + if !cix.data.is_empty() { + prost_bytes_encoded_len(3u32, &cix.data) + } else { + 0 + } + } + + fn atl_encoded_len(atl: &MessageAddressTableLookup) -> usize { + prost_bytes_encoded_len(1u32, atl.account_key.as_ref()) + + if !atl.writable_indexes.is_empty() { + prost_bytes_encoded_len(2u32, &atl.writable_indexes) + } else { + 0 + } + + if !atl.readonly_indexes.is_empty() { + prost_bytes_encoded_len(3u32, &atl.readonly_indexes) + } else { + 0 + } + } + + fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { + let err = convert_to::create_transaction_error(&meta.status); + + err.map_or(0, |msg| ::prost::encoding::message::encoded_len(1u32, &msg)) + + if meta.fee != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &meta.fee) + } else { + 0 + } + + ::prost::encoding::uint64::encoded_len_packed(3u32, &meta.pre_balances) + + ::prost::encoding::uint64::encoded_len_packed(4u32, &meta.post_balances) + + if let Some(ixs) = &meta.inner_instructions { + key_len(5u32) * ixs.len() + + ixs + .iter() + .map(Self::ixs_encoded_len) + .map(|len| encoded_len_varint(len as u64) + len) + .sum::() + } else { + 0 + } + + if let Some(log_messages) = &meta.log_messages { + ::prost::encoding::string::encoded_len_repeated(6u32, log_messages) + } else { + 0 + } + + if let Some(pre_token_balances) = &meta.pre_token_balances { + prost_message_repeated_encoded_len!( + 7u32, + pre_token_balances, + Self::token_balance_encoded_len + ) + } else { + 0 + } + + if let Some(post_token_balances) = &meta.post_token_balances { + prost_message_repeated_encoded_len!( + 8u32, + post_token_balances, + Self::token_balance_encoded_len + ) + } else { + 0 + } + + if let Some(rewards) = &meta.rewards { + prost_message_repeated_encoded_len!( + 9u32, + rewards, + MessageBlockMeta::reward_encoded_len + ) + } else { + 0 + } + + if meta.inner_instructions.is_none() { + ::prost::encoding::bool::encoded_len(10u32, &true) + } else { + 0 + } + + if meta.log_messages.is_none() { + ::prost::encoding::bool::encoded_len(11u32, &true) + } else { + 0 + } + + prost_message_repeated_encoded_len!(12u32, meta.loaded_addresses.writable, |pk| pk + .as_ref() + .len()) + + prost_message_repeated_encoded_len!(13u32, meta.loaded_addresses.readonly, |pk| pk + .as_ref() + .len()) + + if let Some(rd) = &meta.return_data { + let len = Self::return_data_encoded_len(rd); + key_len(14u32) + encoded_len_varint(len as u64) + len + } else { + 0 + } + + if meta.return_data.is_none() { + ::prost::encoding::bool::encoded_len(15u32, &true) + } else { + 0 + } + + meta.compute_units_consumed.as_ref().map_or(0, |value| { + ::prost::encoding::uint64::encoded_len(16u32, value) + }) + } + + fn ixs_encoded_len(ixs: &InnerInstructions) -> usize { + let index = ixs.index as u32; + (if index != 0u32 { + ::prost::encoding::uint32::encoded_len(1u32, &index) + } else { + 0 + }) + prost_message_repeated_encoded_len!(2u32, &ixs.instructions, Self::ix_encoded_len) } - const fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { - 0 + fn ix_encoded_len(ix: &InnerInstruction) -> usize { + let program_id_index = ix.instruction.program_id_index as u32; + (if program_id_index != 0u32 { + ::prost::encoding::uint32::encoded_len(1u32, &program_id_index) + } else { + 0 + }) + if !ix.instruction.accounts.is_empty() { + prost_bytes_encoded_len(2u32, &ix.instruction.accounts) + } else { + 0 + } + if !ix.instruction.data.is_empty() { + prost_bytes_encoded_len(3u32, &ix.instruction.data) + } else { + 0 + } + ix.stack_height.map_or(0, |value| { + ::prost::encoding::uint32::encoded_len(4u32, &value) + }) + } + + fn token_balance_encoded_len(balance: &TransactionTokenBalance) -> usize { + let account_index = balance.account_index as u32; + let ui_token_amount_len = Self::ui_token_amount_encoded_len(&balance.ui_token_amount); + + (if account_index != 0u32 { + ::prost::encoding::uint32::encoded_len(1u32, &account_index) + } else { + 0 + }) + if !balance.mint.is_empty() { + ::prost::encoding::string::encoded_len(2u32, &balance.mint) + } else { + 0 + } + key_len(3u32) + + encoded_len_varint(ui_token_amount_len as u64) + + ui_token_amount_len + + if !balance.owner.is_empty() { + ::prost::encoding::string::encoded_len(4u32, &balance.owner) + } else { + 0 + } + + if !balance.program_id.is_empty() { + ::prost::encoding::string::encoded_len(5u32, &balance.program_id) + } else { + 0 + } + } + + fn ui_token_amount_encoded_len(amount: &UiTokenAmount) -> usize { + let ui_amount = amount.ui_amount.unwrap_or_default(); + let decimals = amount.decimals as u32; + + (if ui_amount != 0f64 { + ::prost::encoding::double::encoded_len(1u32, &ui_amount) + } else { + 0 + }) + if decimals != 0u32 { + ::prost::encoding::uint32::encoded_len(2u32, &decimals) + } else { + 0 + } + if !amount.amount.is_empty() { + ::prost::encoding::string::encoded_len(3u32, &amount.amount) + } else { + 0 + } + if !amount.ui_amount_string.is_empty() { + ::prost::encoding::string::encoded_len(4u32, &amount.ui_amount_string) + } else { + 0 + } + } + + fn return_data_encoded_len(return_data: &TransactionReturnData) -> usize { + prost_bytes_encoded_len(1u32, return_data.program_id.as_ref()) + + if !return_data.data.is_empty() { + ::prost::encoding::bytes::encoded_len(2u32, &return_data.data) + } else { + 0 + } } } @@ -801,7 +1110,7 @@ impl MessageBlockMeta { ::prost::encoding::int32::encoded_len(4u32, &reward_type) } else { 0 - } + if commission != b"" { + } + if !commission.is_empty() { prost_bytes_encoded_len(5u32, commission) } else { 0 @@ -918,18 +1227,6 @@ impl prost::Message for MessageEntry { } } -#[inline] -fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { - encode_key(tag, WireType::LengthDelimited, buf); - encode_varint(value.len() as u64, buf); - buf.put(value); -} - -#[inline] -pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { - key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() -} - #[cfg(any(test, feature = "plugin-bench"))] pub mod tests { #![cfg_attr(feature = "plugin-bench", allow(dead_code))] @@ -1059,6 +1356,12 @@ pub mod tests { message, }; let update = SubscribeUpdate::from(&msg); + // + // let len1 = msg.encoded_len(); + // let len2 = update.encoded_len(); + // if len1 != len2 { + // println!("my {len1} vs proto {len2}"); + // } assert_eq!(msg.encoded_len(), update.encoded_len()); assert_eq!( SubscribeUpdate::decode(msg.encode_to_vec().as_slice()).expect("failed to decode"), @@ -1178,7 +1481,7 @@ pub mod tests { transaction: Arc::clone(tx), slot: 42, }; - encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); + // encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); } } From 6b87cc9510ca3239d0e27ac17c88dcabb9b6970a Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 20:05:23 +0200 Subject: [PATCH 30/50] use inline fns --- .../src/plugin/message_ref.rs | 178 +++++++----------- 1 file changed, 67 insertions(+), 111 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index d20ab8ed..aaaee6c1 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -49,9 +49,14 @@ fn prost_bytes_encode_raw(tag: u32, value: &[u8], buf: &mut impl BufMut) { buf.put(value); } +#[inline] +pub fn prost_field_encoded_len(tag: u32, len: usize) -> usize { + key_len(tag) + encoded_len_varint(len as u64) + len +} + #[inline] pub fn prost_bytes_encoded_len(tag: u32, value: &[u8]) -> usize { - key_len(tag) + encoded_len_varint(value.len() as u64) + value.len() + prost_field_encoded_len(tag, value.len()) } macro_rules! prost_message_repeated_encoded_len { @@ -95,13 +100,7 @@ impl prost::Message for Message { } fn encoded_len(&self) -> usize { - key_len(1u32) * self.filters.len() - + self - .filters - .iter() - .map(|filter| filter.as_ref().len()) - .map(|len| encoded_len_varint(len as u64) + len) - .sum::() + prost_message_repeated_encoded_len!(1u32, self.filters, |filter| filter.as_ref().len()) + self.message.encoded_len() } @@ -289,20 +288,18 @@ impl prost::Message for MessageAccountRef { } fn encoded_len(&self) -> usize { - let len = Self::account_encoded_len(self.account.as_ref(), &self.data_slice); - key_len(1u32) - + encoded_len_varint(len as u64) - + len - + if self.slot != 0u64 { - ::prost::encoding::uint64::encoded_len(2u32, &self.slot) - } else { - 0 - } - + if self.is_startup { - ::prost::encoding::bool::encoded_len(3u32, &self.is_startup) - } else { - 0 - } + prost_field_encoded_len( + 1u32, + Self::account_encoded_len(self.account.as_ref(), &self.data_slice), + ) + if self.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(2u32, &self.slot) + } else { + 0 + } + if self.is_startup { + ::prost::encoding::bool::encoded_len(3u32, &self.is_startup) + } else { + 0 + } } fn merge_field( @@ -391,6 +388,7 @@ impl MessageAccountRef { data_slice: &FilterAccountsDataSlice, ) -> usize { let data_len = Self::account_data_slice_len(account, data_slice); + prost_bytes_encoded_len(1u32, account.pubkey.as_ref()) + if account.lamports != 0u64 { ::prost::encoding::uint64::encoded_len(2u32, &account.lamports) @@ -408,8 +406,8 @@ impl MessageAccountRef { } else { 0 } - + if data_len > 0 { - key_len(6u32) + encoded_len_varint(data_len as u64) + data_len + + if data_len != 0 { + prost_field_encoded_len(6u32, data_len) } else { 0 } @@ -420,7 +418,7 @@ impl MessageAccountRef { } + account .txn_signature - .map_or(0, |s| prost_bytes_encoded_len(8u32, s.as_ref())) + .map_or(0, |sig| prost_bytes_encoded_len(8u32, sig.as_ref())) } } @@ -450,6 +448,7 @@ impl prost::Message for MessageSlot { fn encoded_len(&self) -> usize { let status = CommitmentLevelProto::from(self.status) as i32; + (if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { @@ -505,10 +504,7 @@ impl prost::Message for MessageTransactionRef { } fn encoded_len(&self) -> usize { - let len = Self::tx_meta_encoded_len(&self.transaction); - key_len(1u32) - + encoded_len_varint(len as u64) - + len + prost_field_encoded_len(1u32, Self::tx_meta_encoded_len(&self.transaction)) + if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(2u32, &self.slot) } else { @@ -534,20 +530,15 @@ impl prost::Message for MessageTransactionRef { impl MessageTransactionRef { fn tx_meta_encoded_len(tx: &MessageTransactionInfo) -> usize { let index = tx.index as u64; + prost_bytes_encoded_len(1u32, tx.signature.as_ref()) + if tx.is_vote { ::prost::encoding::bool::encoded_len(2u32, &tx.is_vote) } else { 0 } - + { - let len = Self::tx_encoded_len(&tx.transaction); - key_len(3u32) + encoded_len_varint(len as u64) + len - } - + { - let len = Self::meta_encoded_len(&tx.meta); - key_len(4u32) + encoded_len_varint(len as u64) + len - } + + prost_field_encoded_len(3u32, Self::tx_encoded_len(&tx.transaction)) + + prost_field_encoded_len(4u32, Self::meta_encoded_len(&tx.meta)) + if index != 0u64 { ::prost::encoding::uint64::encoded_len(5u32, &index) } else { @@ -556,12 +547,8 @@ impl MessageTransactionRef { } fn tx_encoded_len(tx: &SanitizedTransaction) -> usize { - let message_len = Self::message_encoded_len(tx.message()); - let signatures = tx.signatures(); - prost_message_repeated_encoded_len!(1u32, signatures, |sig| sig.as_ref().len()) - + key_len(2u32) - + encoded_len_varint(message_len as u64) - + message_len + prost_message_repeated_encoded_len!(1u32, tx.signatures(), |sig| sig.as_ref().len()) + + prost_field_encoded_len(2u32, Self::message_encoded_len(tx.message())) } fn message_encoded_len(message: &SanitizedMessage) -> usize { @@ -584,35 +571,17 @@ impl MessageTransactionRef { ), }; - let header_len = Self::header_encoded_len(header); - key_len(1u32) - + encoded_len_varint(header_len as u64) - + header_len - + key_len(2u32) * account_keys.len() - + account_keys - .iter() - .map(|account_key| account_key.as_ref().len()) - .map(|len| encoded_len_varint(len as u64) + len) - .sum::() + prost_field_encoded_len(1u32, Self::header_encoded_len(header)) + + prost_message_repeated_encoded_len!(2u32, account_keys, |key| key.as_ref().len()) + prost_bytes_encoded_len(3u32, recent_blockhash.as_ref()) - + key_len(4u32) * cixs.len() - + cixs - .iter() - .map(Self::cix_encoded_len) - .map(|len| encoded_len_varint(len as u64) + len) - .sum::() + + prost_message_repeated_encoded_len!(4u32, cixs, Self::cix_encoded_len) + if versioned { ::prost::encoding::bool::encoded_len(5u32, &versioned) } else { 0 } + if let Some(atls) = atls { - key_len(6u32) * atls.len() - + atls - .iter() - .map(Self::atl_encoded_len) - .map(|len| encoded_len_varint(len as u64) + len) - .sum::() + prost_message_repeated_encoded_len!(6u32, atls, Self::atl_encoded_len) } else { 0 } @@ -622,6 +591,7 @@ impl MessageTransactionRef { let num_required_signatures = header.num_required_signatures as u32; let num_readonly_signed_accounts = header.num_readonly_signed_accounts as u32; let num_readonly_unsigned_accounts = header.num_readonly_unsigned_accounts as u32; + (if num_required_signatures != 0u32 { ::prost::encoding::uint32::encoded_len(1u32, &num_required_signatures) } else { @@ -639,6 +609,7 @@ impl MessageTransactionRef { fn cix_encoded_len(cix: &CompiledInstruction) -> usize { let program_id_index = cix.program_id_index as u32; + (if program_id_index != 0u32 { ::prost::encoding::uint32::encoded_len(1u32, &program_id_index) } else { @@ -669,9 +640,8 @@ impl MessageTransactionRef { } fn meta_encoded_len(meta: &TransactionStatusMeta) -> usize { - let err = convert_to::create_transaction_error(&meta.status); - - err.map_or(0, |msg| ::prost::encoding::message::encoded_len(1u32, &msg)) + convert_to::create_transaction_error(&meta.status) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(1u32, &msg)) + if meta.fee != 0u64 { ::prost::encoding::uint64::encoded_len(2u32, &meta.fee) } else { @@ -680,12 +650,7 @@ impl MessageTransactionRef { + ::prost::encoding::uint64::encoded_len_packed(3u32, &meta.pre_balances) + ::prost::encoding::uint64::encoded_len_packed(4u32, &meta.post_balances) + if let Some(ixs) = &meta.inner_instructions { - key_len(5u32) * ixs.len() - + ixs - .iter() - .map(Self::ixs_encoded_len) - .map(|len| encoded_len_varint(len as u64) + len) - .sum::() + prost_message_repeated_encoded_len!(5u32, ixs, Self::ixs_encoded_len) } else { 0 } @@ -738,8 +703,7 @@ impl MessageTransactionRef { .as_ref() .len()) + if let Some(rd) = &meta.return_data { - let len = Self::return_data_encoded_len(rd); - key_len(14u32) + encoded_len_varint(len as u64) + len + prost_field_encoded_len(14u32, Self::return_data_encoded_len(rd)) } else { 0 } @@ -764,6 +728,7 @@ impl MessageTransactionRef { fn ix_encoded_len(ix: &InnerInstruction) -> usize { let program_id_index = ix.instruction.program_id_index as u32; + (if program_id_index != 0u32 { ::prost::encoding::uint32::encoded_len(1u32, &program_id_index) } else { @@ -783,7 +748,6 @@ impl MessageTransactionRef { fn token_balance_encoded_len(balance: &TransactionTokenBalance) -> usize { let account_index = balance.account_index as u32; - let ui_token_amount_len = Self::ui_token_amount_encoded_len(&balance.ui_token_amount); (if account_index != 0u32 { ::prost::encoding::uint32::encoded_len(1u32, &account_index) @@ -793,19 +757,18 @@ impl MessageTransactionRef { ::prost::encoding::string::encoded_len(2u32, &balance.mint) } else { 0 - } + key_len(3u32) - + encoded_len_varint(ui_token_amount_len as u64) - + ui_token_amount_len - + if !balance.owner.is_empty() { - ::prost::encoding::string::encoded_len(4u32, &balance.owner) - } else { - 0 - } - + if !balance.program_id.is_empty() { - ::prost::encoding::string::encoded_len(5u32, &balance.program_id) - } else { - 0 - } + } + prost_field_encoded_len( + 3u32, + Self::ui_token_amount_encoded_len(&balance.ui_token_amount), + ) + if !balance.owner.is_empty() { + ::prost::encoding::string::encoded_len(4u32, &balance.owner) + } else { + 0 + } + if !balance.program_id.is_empty() { + ::prost::encoding::string::encoded_len(5u32, &balance.program_id) + } else { + 0 + } } fn ui_token_amount_encoded_len(amount: &UiTokenAmount) -> usize { @@ -881,6 +844,8 @@ impl prost::Message for MessageTransactionStatusRef { fn encoded_len(&self) -> usize { let tx = &self.transaction; + let index = tx.index as u64; + (if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { @@ -891,13 +856,10 @@ impl prost::Message for MessageTransactionStatusRef { } else { 0 } - + { - let index = tx.index as u64; - if index != 0u64 { - ::prost::encoding::uint64::encoded_len(4u32, &index) - } else { - 0 - } + + if index != 0u64 { + ::prost::encoding::uint64::encoded_len(4u32, &index) + } else { + 0 } + convert_to::create_transaction_error(&tx.meta.status) .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) @@ -993,13 +955,11 @@ impl prost::Message for MessageBlockMeta { ::prost::encoding::string::encoded_len(2u32, &self.blockhash) } else { 0 - } + { - let len = self.rewards_encoded_len(); - key_len(3u32) + encoded_len_varint(len as u64) + len - } + self - .block_time - .map(convert_to::create_timestamp) - .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, &msg)) + } + prost_field_encoded_len(3u32, self.rewards_encoded_len()) + + self + .block_time + .map(convert_to::create_timestamp) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, &msg)) + self .block_height .map(convert_to::create_block_height) @@ -1078,13 +1038,7 @@ impl MessageBlockMeta { } fn rewards_encoded_len(&self) -> usize { - key_len(1u32) * self.rewards.len() - + self - .rewards - .iter() - .map(Self::reward_encoded_len) - .map(|len| len + encoded_len_varint(len as u64)) - .sum::() + prost_message_repeated_encoded_len!(1u32, self.rewards, Self::reward_encoded_len) + self .num_partitions .map(convert_to::create_num_partitions) @@ -1094,6 +1048,7 @@ impl MessageBlockMeta { fn reward_encoded_len(reward: &Reward) -> usize { let reward_type = convert_to::create_reward_type(reward.reward_type) as i32; let commission = Self::commission_to_str(reward.commission); + (if !reward.pubkey.is_empty() { ::prost::encoding::string::encoded_len(1u32, &reward.pubkey) } else { @@ -1187,6 +1142,7 @@ impl prost::Message for MessageEntry { fn encoded_len(&self) -> usize { let index = self.index as u64; + (if self.slot != 0u64 { ::prost::encoding::uint64::encoded_len(1u32, &self.slot) } else { From 60ef1dcdb5b079fce5b1939190975df70768b4a9 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 20:15:11 +0200 Subject: [PATCH 31/50] fix tx encoded_len --- yellowstone-grpc-proto/src/plugin/message_ref.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index aaaee6c1..382a4cc8 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -558,7 +558,7 @@ impl MessageTransactionRef { &message.account_keys, &message.recent_blockhash, &message.instructions, - true, + false, None, ), SanitizedMessage::V0(LoadedMessage { message, .. }) => ( @@ -566,7 +566,7 @@ impl MessageTransactionRef { &message.account_keys, &message.recent_blockhash, &message.instructions, - false, + true, Some(&message.address_table_lookups), ), }; @@ -1437,7 +1437,7 @@ pub mod tests { transaction: Arc::clone(tx), slot: 42, }; - // encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); + encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); } } From 03aed5dfb31dbff0455b93f52094506ff97155c2 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 20:45:46 +0200 Subject: [PATCH 32/50] add clone bench --- yellowstone-grpc-proto/benches/encode.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index aece074d..0cec43e3 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -45,6 +45,23 @@ fn bench_account(c: &mut Criterion) { }) }, ); + + let accounts = accounts + .iter() + .map(|(account, data_slice)| build_subscribe_update_account(account, data_slice)) + .collect::>(); + c.bench_with_input( + BenchmarkId::new("accounts", "prost clone"), + &(&accounts, &filters), + |b, (accounts, filters)| { + b.iter(|| { + for account in accounts.iter() { + let msg = build_subscribe_update(filters, account.clone()); + msg.encode_to_vec().len(); + } + }) + }, + ); } criterion_group!( From fb679228a7a9a2925dce17ce6068294f9d21b1eb Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 22:57:58 +0200 Subject: [PATCH 33/50] impl tx encode raw --- .../src/plugin/message_ref.rs | 289 +++++++++++++++++- 1 file changed, 283 insertions(+), 6 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 382a4cc8..433236dc 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -360,6 +360,7 @@ impl MessageAccountRef { ) { encode_key(tag, WireType::LengthDelimited, buf); encode_varint(Self::account_encoded_len(account, data_slice) as u64, buf); + prost_bytes_encode_raw(1u32, account.pubkey.as_ref(), buf); if account.lamports != 0u64 { ::prost::encoding::uint64::encode(2u32, &account.lamports, buf); @@ -500,7 +501,10 @@ impl From<&MessageTransactionRef> for SubscribeUpdateTransaction { impl prost::Message for MessageTransactionRef { fn encode_raw(&self, buf: &mut impl BufMut) { - todo!() + Self::tx_meta_encode_raw(1u32, &self.transaction, buf); + if self.slot != 0u64 { + ::prost::encoding::uint64::encode(2u32, &self.slot, buf); + } } fn encoded_len(&self) -> usize { @@ -528,6 +532,277 @@ impl prost::Message for MessageTransactionRef { } impl MessageTransactionRef { + fn tx_meta_encode_raw(tag: u32, tx: &MessageTransactionInfo, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::tx_meta_encoded_len(tx) as u64, buf); + + let index = tx.index as u64; + + prost_bytes_encode_raw(1u32, tx.signature.as_ref(), buf); + if tx.is_vote { + ::prost::encoding::bool::encode(2u32, &tx.is_vote, buf); + } + Self::tx_encode_raw(3u32, &tx.transaction, buf); + Self::meta_encode_raw(4u32, &tx.meta, buf); + if index != 0u64 { + ::prost::encoding::uint64::encode(5u32, &index, buf); + } + } + + fn tx_encode_raw(tag: u32, tx: &SanitizedTransaction, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::tx_encoded_len(tx) as u64, buf); + + for sig in tx.signatures() { + prost_bytes_encode_raw(1u32, sig.as_ref(), buf); + } + Self::message_encode_raw(2u32, tx.message(), buf); + } + + fn message_encode_raw(tag: u32, message: &SanitizedMessage, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::message_encoded_len(message) as u64, buf); + + let (header, account_keys, recent_blockhash, cixs, versioned, atls) = match message { + SanitizedMessage::Legacy(LegacyMessage { message, .. }) => ( + message.header, + &message.account_keys, + &message.recent_blockhash, + &message.instructions, + false, + None, + ), + SanitizedMessage::V0(LoadedMessage { message, .. }) => ( + message.header, + &message.account_keys, + &message.recent_blockhash, + &message.instructions, + true, + Some(&message.address_table_lookups), + ), + }; + + Self::header_encode_raw(1u32, header, buf); + for pubkey in account_keys { + prost_bytes_encode_raw(2u32, pubkey.as_ref(), buf); + } + prost_bytes_encode_raw(3u32, recent_blockhash.as_ref(), buf); + for cix in cixs { + Self::cix_encode_raw(4u32, cix, buf); + } + if versioned { + ::prost::encoding::bool::encode(5u32, &versioned, buf); + } + if let Some(atls) = atls { + for atl in atls { + Self::atl_encode_raw(6u32, atl, buf); + } + } + } + + fn header_encode_raw(tag: u32, header: MessageHeader, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::header_encoded_len(header) as u64, buf); + + let num_required_signatures = header.num_required_signatures as u32; + let num_readonly_signed_accounts = header.num_readonly_signed_accounts as u32; + let num_readonly_unsigned_accounts = header.num_readonly_unsigned_accounts as u32; + + if num_required_signatures != 0u32 { + ::prost::encoding::uint32::encode(1u32, &num_required_signatures, buf); + } + if num_readonly_signed_accounts != 0u32 { + ::prost::encoding::uint32::encode(2u32, &num_readonly_signed_accounts, buf); + } + if num_readonly_unsigned_accounts != 0u32 { + ::prost::encoding::uint32::encode(3u32, &num_readonly_unsigned_accounts, buf); + } + } + + fn cix_encode_raw(tag: u32, cix: &CompiledInstruction, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::cix_encoded_len(cix) as u64, buf); + + let program_id_index = cix.program_id_index as u32; + + if program_id_index != 0u32 { + ::prost::encoding::uint32::encode(1u32, &program_id_index, buf); + } + if !cix.accounts.is_empty() { + ::prost::encoding::bytes::encode(2u32, &cix.accounts, buf); + } + if !cix.data.is_empty() { + ::prost::encoding::bytes::encode(3u32, &cix.data, buf); + } + } + + fn atl_encode_raw(tag: u32, atl: &MessageAddressTableLookup, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::atl_encoded_len(atl) as u64, buf); + + prost_bytes_encode_raw(1u32, atl.account_key.as_ref(), buf); + if !atl.writable_indexes.is_empty() { + prost_bytes_encode_raw(2u32, atl.writable_indexes.as_ref(), buf); + } + if !atl.readonly_indexes.is_empty() { + prost_bytes_encode_raw(3u32, atl.readonly_indexes.as_ref(), buf); + } + } + + fn meta_encode_raw(tag: u32, meta: &TransactionStatusMeta, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::meta_encoded_len(meta) as u64, buf); + + let err = convert_to::create_transaction_error(&meta.status); + + if let Some(msg) = err { + ::prost::encoding::message::encode(1u32, &msg, buf); + } + if meta.fee != 0u64 { + ::prost::encoding::uint64::encode(2u32, &meta.fee, buf); + } + ::prost::encoding::uint64::encode_packed(3u32, &meta.pre_balances, buf); + ::prost::encoding::uint64::encode_packed(4u32, &meta.post_balances, buf); + if let Some(vec) = &meta.inner_instructions { + for ixs in vec { + Self::ixs_encode_raw(5u32, ixs, buf); + } + } + if let Some(log_messages) = &meta.log_messages { + ::prost::encoding::string::encode_repeated(6u32, log_messages, buf); + } + if let Some(vec) = &meta.pre_token_balances { + for pre_token_balances in vec { + Self::token_balance_encode_raw(7u32, pre_token_balances, buf); + } + } + if let Some(vec) = &meta.post_token_balances { + for post_token_balances in vec { + Self::token_balance_encode_raw(8u32, post_token_balances, buf); + } + } + if let Some(vec) = &meta.rewards { + for reward in vec { + MessageBlockMeta::reward_encode_raw(9u32, reward, buf); + } + } + if meta.inner_instructions.is_none() { + ::prost::encoding::bool::encode(10u32, &true, buf); + } + if meta.log_messages.is_none() { + ::prost::encoding::bool::encode(11u32, &true, buf); + } + for pubkey in meta.loaded_addresses.writable.iter() { + prost_bytes_encode_raw(12u32, pubkey.as_ref(), buf); + } + for pubkey in meta.loaded_addresses.readonly.iter() { + prost_bytes_encode_raw(13u32, pubkey.as_ref(), buf); + } + if let Some(rd) = &meta.return_data { + Self::return_data_encode_raw(14u32, rd, buf); + } + if meta.return_data.is_none() { + ::prost::encoding::bool::encode(15u32, &true, buf); + } + if let Some(value) = &meta.compute_units_consumed { + ::prost::encoding::uint64::encode(16u32, value, buf); + } + } + + fn ixs_encode_raw(tag: u32, ixs: &InnerInstructions, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::ixs_encoded_len(ixs) as u64, buf); + + let index = ixs.index as u32; + + if index != 0u32 { + ::prost::encoding::uint32::encode(1u32, &index, buf); + } + for ix in ixs.instructions.iter() { + Self::ix_encode_raw(2u32, ix, buf); + } + } + + fn ix_encode_raw(tag: u32, ix: &InnerInstruction, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::ix_encoded_len(ix) as u64, buf); + + let program_id_index = ix.instruction.program_id_index as u32; + + if program_id_index != 0u32 { + ::prost::encoding::uint32::encode(1u32, &program_id_index, buf); + } + if !ix.instruction.accounts.is_empty() { + prost_bytes_encode_raw(2u32, &ix.instruction.accounts, buf); + } + if !ix.instruction.data.is_empty() { + prost_bytes_encode_raw(3u32, &ix.instruction.data, buf); + } + if let Some(value) = &ix.stack_height { + ::prost::encoding::uint32::encode(4u32, value, buf); + } + } + + fn token_balance_encode_raw( + tag: u32, + balance: &TransactionTokenBalance, + buf: &mut impl BufMut, + ) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::token_balance_encoded_len(balance) as u64, buf); + + let account_index = balance.account_index as u32; + + if account_index != 0u32 { + ::prost::encoding::uint32::encode(1u32, &account_index, buf); + } + if !balance.mint.is_empty() { + ::prost::encoding::string::encode(2u32, &balance.mint, buf); + } + Self::ui_token_amount_encode_raw(3u32, &balance.ui_token_amount, buf); + if !balance.owner.is_empty() { + ::prost::encoding::string::encode(4u32, &balance.owner, buf); + } + if !balance.program_id.is_empty() { + ::prost::encoding::string::encode(5u32, &balance.program_id, buf); + } + } + + fn ui_token_amount_encode_raw(tag: u32, amount: &UiTokenAmount, buf: &mut impl BufMut) { + let ui_amount = amount.ui_amount.unwrap_or_default(); + let decimals = amount.decimals as u32; + + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::ui_token_amount_encoded_len(amount) as u64, buf); + + if ui_amount != 0f64 { + ::prost::encoding::double::encode(1u32, &ui_amount, buf); + } + if decimals != 0u32 { + ::prost::encoding::uint32::encode(2u32, &decimals, buf); + } + if !amount.amount.is_empty() { + ::prost::encoding::string::encode(3u32, &amount.amount, buf); + } + if !amount.ui_amount_string.is_empty() { + ::prost::encoding::string::encode(4u32, &amount.ui_amount_string, buf); + } + } + + fn return_data_encode_raw( + tag: u32, + return_data: &TransactionReturnData, + buf: &mut impl BufMut, + ) { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(Self::return_data_encoded_len(return_data) as u64, buf); + + prost_bytes_encode_raw(1u32, return_data.program_id.as_ref(), buf); + if !return_data.data.is_empty() { + ::prost::encoding::bytes::encode(2u32, &return_data.data, buf); + } + } + fn tx_meta_encoded_len(tx: &MessageTransactionInfo) -> usize { let index = tx.index as u64; @@ -719,6 +994,7 @@ impl MessageTransactionRef { fn ixs_encoded_len(ixs: &InnerInstructions) -> usize { let index = ixs.index as u32; + (if index != 0u32 { ::prost::encoding::uint32::encoded_len(1u32, &index) } else { @@ -923,7 +1199,7 @@ impl prost::Message for MessageBlockMeta { if !self.blockhash.is_empty() { ::prost::encoding::string::encode(2u32, &self.blockhash, buf); } - self.rewards_encode(3u32, buf); + self.rewards_encode_raw(3u32, buf); if let Some(block_time) = self.block_time { let msg = convert_to::create_timestamp(block_time); ::prost::encoding::message::encode(4u32, &msg, buf); @@ -1002,11 +1278,12 @@ impl prost::Message for MessageBlockMeta { } impl MessageBlockMeta { - fn rewards_encode(&self, tag: u32, buf: &mut impl BufMut) { + fn rewards_encode_raw(&self, tag: u32, buf: &mut impl BufMut) { encode_key(tag, WireType::LengthDelimited, buf); encode_varint(self.rewards_encoded_len() as u64, buf); + for reward in &self.rewards { - Self::reward_encode(reward, buf); + Self::reward_encode_raw(1u32, reward, buf); } if let Some(num_partitions) = self.num_partitions { let msg = convert_to::create_num_partitions(num_partitions); @@ -1014,8 +1291,8 @@ impl MessageBlockMeta { } } - fn reward_encode(reward: &Reward, buf: &mut impl BufMut) { - encode_key(1u32, WireType::LengthDelimited, buf); + fn reward_encode_raw(tag: u32, reward: &Reward, buf: &mut impl BufMut) { + encode_key(tag, WireType::LengthDelimited, buf); encode_varint(Self::reward_encoded_len(reward) as u64, buf); if !reward.pubkey.is_empty() { From 8d176a6aafce87871e1161a5f8e5f1a95dca08f4 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 23:02:32 +0200 Subject: [PATCH 34/50] rm allocations --- yellowstone-grpc-proto/src/plugin/message_ref.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 433236dc..1bbe4030 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -629,10 +629,10 @@ impl MessageTransactionRef { ::prost::encoding::uint32::encode(1u32, &program_id_index, buf); } if !cix.accounts.is_empty() { - ::prost::encoding::bytes::encode(2u32, &cix.accounts, buf); + prost_bytes_encode_raw(2u32, cix.accounts.as_ref(), buf); } if !cix.data.is_empty() { - ::prost::encoding::bytes::encode(3u32, &cix.data, buf); + prost_bytes_encode_raw(3u32, cix.data.as_ref(), buf); } } @@ -799,7 +799,7 @@ impl MessageTransactionRef { prost_bytes_encode_raw(1u32, return_data.program_id.as_ref(), buf); if !return_data.data.is_empty() { - ::prost::encoding::bytes::encode(2u32, &return_data.data, buf); + prost_bytes_encode_raw(2u32, return_data.data.as_ref(), buf); } } @@ -1073,7 +1073,7 @@ impl MessageTransactionRef { fn return_data_encoded_len(return_data: &TransactionReturnData) -> usize { prost_bytes_encoded_len(1u32, return_data.program_id.as_ref()) + if !return_data.data.is_empty() { - ::prost::encoding::bytes::encoded_len(2u32, &return_data.data) + prost_bytes_encoded_len(2u32, return_data.data.as_ref()) } else { 0 } From 2ec5a386b1025974b37ccdc468dc4dd68ca5b593 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 8 Nov 2024 23:47:58 +0200 Subject: [PATCH 35/50] conv --- yellowstone-grpc-geyser/src/filters.rs | 4 +- yellowstone-grpc-proto/benches/encode.rs | 32 +- .../src/plugin/message_ref.rs | 326 ++++++------------ 3 files changed, 128 insertions(+), 234 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 4e8aaa7a..71739998 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -867,7 +867,7 @@ impl FilterBlocks { fn get_filters( &self, message: &Arc, - accounts_data_slice: &[Range], + accounts_data_slice: &FilterAccountsDataSlice, ) -> FilteredMessages { let mut messages = FilteredMessages::new(); for (filter, inner) in self.filters.iter() { @@ -931,7 +931,7 @@ impl FilterBlocks { meta: Arc::clone(&message.meta), transactions, updated_account_count: message.updated_account_count, - accounts_data_slice: accounts_data_slice.to_vec(), + accounts_data_slice: accounts_data_slice.clone(), accounts, entries, }), diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index 0cec43e3..229f30a3 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -2,15 +2,37 @@ use { criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}, prost::Message as _, std::time::Duration, - yellowstone_grpc_proto::plugin::message_ref::{ - tests::{ - build_subscribe_update, build_subscribe_update_account, create_accounts, - create_message_filters, + yellowstone_grpc_proto::{ + geyser::{subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateAccount}, + plugin::{ + filter::FilterAccountsDataSlice, + message::MessageAccount, + message_ref::{ + tests::{create_accounts, create_message_filters}, + Message, MessageFilters, MessageRef, + }, }, - Message, MessageRef, }, }; +fn build_subscribe_update(filters: &MessageFilters, update: UpdateOneof) -> SubscribeUpdate { + SubscribeUpdate { + filters: filters.iter().map(|f| f.as_ref().to_owned()).collect(), + update_oneof: Some(update), + } +} + +fn build_subscribe_update_account( + message: &MessageAccount, + data_slice: &FilterAccountsDataSlice, +) -> UpdateOneof { + UpdateOneof::Account(SubscribeUpdateAccount { + account: Some((message.account.as_ref(), data_slice).into()), + slot: message.slot, + is_startup: message.is_startup, + }) +} + fn bench_account(c: &mut Criterion) { let accounts = create_accounts(); let filters = create_message_filters(&["my special filter"]); diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 1bbe4030..15a44376 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -11,9 +11,10 @@ use { geyser::{ subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, - SubscribeUpdateBlockMeta, SubscribeUpdateEntry, SubscribeUpdatePing, - SubscribeUpdatePong, SubscribeUpdateSlot, SubscribeUpdateTransaction, - SubscribeUpdateTransactionInfo, SubscribeUpdateTransactionStatus, + SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdateEntry, + SubscribeUpdatePing, SubscribeUpdatePong, SubscribeUpdateSlot, + SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, + SubscribeUpdateTransactionStatus, }, solana::storage::confirmed_block::RewardType as RewardTypeProto, }, @@ -133,7 +134,7 @@ pub enum MessageRef { Slot(MessageSlot), // 3 Transaction(MessageTransactionRef), // 4 TransactionStatus(MessageTransactionStatusRef), // 10 - Block, // 5 + Block(MessageRefBlock), // 5 Ping, // 6 Pong(MessageRefPong), // 9 BlockMeta(Arc), // 7 @@ -147,7 +148,7 @@ impl From<&MessageRef> for UpdateOneof { MessageRef::Slot(msg) => Self::Slot(msg.into()), MessageRef::Transaction(msg) => Self::Transaction(msg.into()), MessageRef::TransactionStatus(msg) => Self::TransactionStatus(msg.into()), - MessageRef::Block => todo!(), + MessageRef::Block(msg) => Self::Block(msg.into()), MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), MessageRef::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), MessageRef::BlockMeta(msg) => Self::BlockMeta(msg.as_ref().into()), @@ -163,7 +164,7 @@ impl prost::Message for MessageRef { MessageRef::Slot(msg) => message::encode(3u32, msg, buf), MessageRef::Transaction(msg) => message::encode(4u32, msg, buf), MessageRef::TransactionStatus(msg) => message::encode(10u32, msg, buf), - MessageRef::Block => todo!(), + MessageRef::Block(msg) => message::encode(5u32, msg, buf), MessageRef::Ping => { encode_key(6u32, WireType::LengthDelimited, buf); encode_varint(0, buf); @@ -180,7 +181,7 @@ impl prost::Message for MessageRef { MessageRef::Slot(msg) => message::encoded_len(3u32, msg), MessageRef::Transaction(msg) => message::encoded_len(4u32, msg), MessageRef::TransactionStatus(msg) => message::encoded_len(10u32, msg), - MessageRef::Block => todo!(), + MessageRef::Block(msg) => message::encoded_len(5u32, msg), MessageRef::Ping => key_len(6u32) + encoded_len_varint(0), MessageRef::Pong(msg) => message::encoded_len(9u32, msg), MessageRef::BlockMeta(msg) => message::encoded_len(7u32, msg.as_ref()), @@ -231,8 +232,8 @@ impl MessageRef { }) } - pub fn block(message: MessageRefBlock) -> Self { - todo!() + pub const fn block(message: MessageRefBlock) -> Self { + Self::Block(message) } pub const fn pong(id: i32) -> Self { @@ -259,23 +260,28 @@ pub struct MessageAccountRef { impl From<&MessageAccountRef> for SubscribeUpdateAccount { fn from(msg: &MessageAccountRef) -> Self { SubscribeUpdateAccount { - account: Some(SubscribeUpdateAccountInfo { - pubkey: msg.account.pubkey.as_ref().into(), - lamports: msg.account.lamports, - owner: msg.account.owner.as_ref().into(), - executable: msg.account.executable, - rent_epoch: msg.account.rent_epoch, - data: MessageAccountRef::accout_data_slice(&msg.account, &msg.data_slice) - .into_owned(), - write_version: msg.account.write_version, - txn_signature: msg.account.txn_signature.map(|s| s.as_ref().into()), - }), + account: Some((msg.account.as_ref(), &msg.data_slice).into()), slot: msg.slot, is_startup: msg.is_startup, } } } +impl From<(&MessageAccountInfo, &FilterAccountsDataSlice)> for SubscribeUpdateAccountInfo { + fn from((account, data_slice): (&MessageAccountInfo, &FilterAccountsDataSlice)) -> Self { + SubscribeUpdateAccountInfo { + pubkey: account.pubkey.as_ref().into(), + lamports: account.lamports, + owner: account.owner.as_ref().into(), + executable: account.executable, + rent_epoch: account.rent_epoch, + data: MessageAccountRef::accout_data_slice(account, data_slice).into_owned(), + write_version: account.write_version, + txn_signature: account.txn_signature.map(|s| s.as_ref().into()), + } + } +} + impl prost::Message for MessageAccountRef { fn encode_raw(&self, buf: &mut impl BufMut) { Self::account_encode(&self.account, &self.data_slice, 1u32, buf); @@ -487,18 +493,24 @@ pub struct MessageTransactionRef { impl From<&MessageTransactionRef> for SubscribeUpdateTransaction { fn from(msg: &MessageTransactionRef) -> Self { Self { - transaction: Some(SubscribeUpdateTransactionInfo { - signature: msg.transaction.signature.as_ref().into(), - is_vote: msg.transaction.is_vote, - transaction: Some(convert_to::create_transaction(&msg.transaction.transaction)), - meta: Some(convert_to::create_transaction_meta(&msg.transaction.meta)), - index: msg.transaction.index as u64, - }), + transaction: Some(msg.transaction.as_ref().into()), slot: msg.slot, } } } +impl From<&MessageTransactionInfo> for SubscribeUpdateTransactionInfo { + fn from(tx: &MessageTransactionInfo) -> Self { + SubscribeUpdateTransactionInfo { + signature: tx.signature.as_ref().into(), + is_vote: tx.is_vote, + transaction: Some(convert_to::create_transaction(&tx.transaction)), + meta: Some(convert_to::create_transaction_meta(&tx.meta)), + index: tx.index as u64, + } + } +} + impl prost::Message for MessageTransactionRef { fn encode_raw(&self, buf: &mut impl BufMut) { Self::tx_meta_encode_raw(1u32, &self.transaction, buf); @@ -1162,10 +1174,69 @@ pub struct MessageRefBlock { pub transactions: Vec>, pub updated_account_count: u64, pub accounts: Vec>, - pub accounts_data_slice: Vec>, + pub accounts_data_slice: FilterAccountsDataSlice, pub entries: Vec>, } +impl From<&MessageRefBlock> for SubscribeUpdateBlock { + fn from(msg: &MessageRefBlock) -> Self { + Self { + slot: msg.meta.slot, + blockhash: msg.meta.blockhash.clone(), + rewards: Some(convert_to::create_rewards_obj( + msg.meta.rewards.as_slice(), + msg.meta.num_partitions, + )), + block_time: msg.meta.block_time.map(convert_to::create_timestamp), + block_height: msg.meta.block_height.map(convert_to::create_block_height), + parent_slot: msg.meta.parent_slot, + parent_blockhash: msg.meta.parent_blockhash.clone(), + executed_transaction_count: msg.meta.executed_transaction_count, + transactions: msg + .transactions + .iter() + .map(|tx| tx.as_ref().into()) + .collect(), + updated_account_count: msg.updated_account_count, + accounts: msg + .accounts + .iter() + .map(|account| (account.as_ref(), &msg.accounts_data_slice).into()) + .collect(), + entries_count: msg.meta.entries_count, + entries: msg + .entries + .iter() + .map(|entry| entry.as_ref().into()) + .collect(), + } + } +} + +impl prost::Message for MessageRefBlock { + fn encode_raw(&self, buf: &mut impl BufMut) { + todo!() + } + + fn encoded_len(&self) -> usize { + todo!() + } + + fn merge_field( + &mut self, + _tag: u32, + _wire_type: WireType, + _buf: &mut impl Buf, + _ctx: DecodeContext, + ) -> Result<(), DecodeError> { + unimplemented!() + } + + fn clear(&mut self) { + unimplemented!() + } +} + #[derive(prost::Message)] pub struct MessageRefPong { #[prost(int32, tag = "1")] @@ -1719,203 +1790,4 @@ pub mod tests { } } } - - // benches - pub fn build_subscribe_update( - filters: &MessageFilters, - update: UpdateOneof, - ) -> SubscribeUpdate { - SubscribeUpdate { - filters: filters.iter().map(|f| f.as_ref().to_owned()).collect(), - update_oneof: Some(update), - } - } - - pub fn build_subscribe_update_account_proto( - message: &MessageAccountInfo, - data_slice: &FilterAccountsDataSlice, - ) -> SubscribeUpdateAccountInfo { - let data = if data_slice.is_empty() { - message.data.clone() - } else { - let mut data = Vec::with_capacity(data_slice.iter().map(|s| s.end - s.start).sum()); - for slice in data_slice { - if message.data.len() >= slice.end { - data.extend_from_slice(&message.data[slice.start..slice.end]); - } - } - data - }; - SubscribeUpdateAccountInfo { - pubkey: message.pubkey.as_ref().into(), - lamports: message.lamports, - owner: message.owner.as_ref().into(), - executable: message.executable, - rent_epoch: message.rent_epoch, - data, - write_version: message.write_version, - txn_signature: message.txn_signature.map(|s| s.as_ref().into()), - } - } - - pub fn build_subscribe_update_account( - message: &MessageAccount, - data_slice: &FilterAccountsDataSlice, - ) -> UpdateOneof { - UpdateOneof::Account(SubscribeUpdateAccount { - account: Some(build_subscribe_update_account_proto( - &message.account, - data_slice, - )), - slot: message.slot, - is_startup: message.is_startup, - }) - } } - -// #[derive(Debug, Clone)] -// pub enum FilteredMessage2<'a> { -// Slot(&'a MessageSlot), -// Account(&'a MessageAccount), -// Transaction(&'a MessageTransaction), -// TransactionStatus(&'a MessageTransaction), -// Entry(&'a MessageEntry), -// Block(MessageBlock), -// BlockMeta(&'a MessageBlockMeta), -// } - -// impl<'a> FilteredMessage2<'a> { -// fn as_proto_account( -// message: &MessageAccountInfo, -// accounts_data_slice: &[Range], -// ) -> SubscribeUpdateAccountInfo { -// let data = if accounts_data_slice.is_empty() { -// message.data.clone() -// } else { -// let mut data = -// Vec::with_capacity(accounts_data_slice.iter().map(|s| s.end - s.start).sum()); -// for slice in accounts_data_slice { -// if message.data.len() >= slice.end { -// data.extend_from_slice(&message.data[slice.start..slice.end]); -// } -// } -// data -// }; -// SubscribeUpdateAccountInfo { -// pubkey: message.pubkey.as_ref().into(), -// lamports: message.lamports, -// owner: message.owner.as_ref().into(), -// executable: message.executable, -// rent_epoch: message.rent_epoch, -// data, -// write_version: message.write_version, -// txn_signature: message.txn_signature.map(|s| s.as_ref().into()), -// } -// } - -// fn as_proto_transaction(message: &MessageTransactionInfo) -> SubscribeUpdateTransactionInfo { -// SubscribeUpdateTransactionInfo { -// signature: message.signature.as_ref().into(), -// is_vote: message.is_vote, -// transaction: Some(convert_to::create_transaction(&message.transaction)), -// meta: Some(convert_to::create_transaction_meta(&message.meta)), -// index: message.index as u64, -// } -// } - -// fn as_proto_entry(message: &MessageEntry) -> SubscribeUpdateEntry { -// SubscribeUpdateEntry { -// slot: message.slot, -// index: message.index as u64, -// num_hashes: message.num_hashes, -// hash: message.hash.into(), -// executed_transaction_count: message.executed_transaction_count, -// starting_transaction_index: message.starting_transaction_index, -// } -// } - -// pub fn as_proto(&self, accounts_data_slice: &[Range]) -> UpdateOneof { -// match self { -// Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot { -// slot: message.slot, -// parent: message.parent, -// status: message.status as i32, -// }), -// Self::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount { -// account: Some(Self::as_proto_account( -// message.account.as_ref(), -// accounts_data_slice, -// )), -// slot: message.slot, -// is_startup: message.is_startup, -// }), -// Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction { -// transaction: Some(Self::as_proto_transaction(message.transaction.as_ref())), -// slot: message.slot, -// }), -// Self::TransactionStatus(message) => { -// UpdateOneof::TransactionStatus(SubscribeUpdateTransactionStatus { -// slot: message.slot, -// signature: message.transaction.signature.as_ref().into(), -// is_vote: message.transaction.is_vote, -// index: message.transaction.index as u64, -// err: match &message.transaction.meta.status { -// Ok(()) => None, -// Err(err) => Some(SubscribeUpdateTransactionError { -// err: bincode::serialize(&err) -// .expect("transaction error to serialize to bytes"), -// }), -// }, -// }) -// } -// Self::Entry(message) => UpdateOneof::Entry(Self::as_proto_entry(message)), -// Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock { -// slot: message.meta.slot, -// blockhash: message.meta.blockhash.clone(), -// rewards: Some(convert_to::create_rewards_obj( -// message.meta.rewards.as_slice(), -// message.meta.num_partitions, -// )), -// block_time: message.meta.block_time.map(convert_to::create_timestamp), -// block_height: message -// .meta -// .block_height -// .map(convert_to::create_block_height), -// parent_slot: message.meta.parent_slot, -// parent_blockhash: message.meta.parent_blockhash.clone(), -// executed_transaction_count: message.meta.executed_transaction_count, -// transactions: message -// .transactions -// .iter() -// .map(|tx| Self::as_proto_transaction(tx.as_ref())) -// .collect(), -// updated_account_count: message.updated_account_count, -// accounts: message -// .accounts -// .iter() -// .map(|acc| Self::as_proto_account(acc.as_ref(), accounts_data_slice)) -// .collect(), -// entries_count: message.meta.entries_count, -// entries: message -// .entries -// .iter() -// .map(|entry| Self::as_proto_entry(entry.as_ref())) -// .collect(), -// }), -// Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta { -// slot: message.slot, -// blockhash: message.blockhash.clone(), -// rewards: Some(convert_to::create_rewards_obj( -// message.rewards.as_slice(), -// message.num_partitions, -// )), -// 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, -// entries_count: message.entries_count, -// }), -// } -// } -// } From 2348522c489a7d5eaefb1cf6a21d6fc41a338fa5 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 9 Nov 2024 00:19:01 +0200 Subject: [PATCH 36/50] bench txs --- yellowstone-grpc-proto/benches/encode.rs | 72 +++++++- .../src/plugin/message_ref.rs | 171 ++++++++++-------- 2 files changed, 166 insertions(+), 77 deletions(-) diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index 229f30a3..9b57ece6 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -1,14 +1,17 @@ use { criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}, prost::Message as _, - std::time::Duration, + std::{sync::Arc, time::Duration}, yellowstone_grpc_proto::{ - geyser::{subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateAccount}, + geyser::{ + subscribe_update::UpdateOneof, SubscribeUpdate, SubscribeUpdateAccount, + SubscribeUpdateTransaction, + }, plugin::{ filter::FilterAccountsDataSlice, - message::MessageAccount, + message::{MessageAccount, MessageTransaction, MessageTransactionInfo}, message_ref::{ - tests::{create_accounts, create_message_filters}, + tests::{create_accounts, create_message_filters, load_predefined_transactions}, Message, MessageFilters, MessageRef, }, }, @@ -33,10 +36,17 @@ fn build_subscribe_update_account( }) } +fn build_subscribe_transaction(transaction: &MessageTransactionInfo, slot: u64) -> UpdateOneof { + UpdateOneof::Transaction(SubscribeUpdateTransaction { + transaction: Some(transaction.into()), + slot, + }) +} + fn bench_account(c: &mut Criterion) { - let accounts = create_accounts(); let filters = create_message_filters(&["my special filter"]); + let accounts = create_accounts(); c.bench_with_input( BenchmarkId::new("accounts", "ref"), &(&accounts, &filters), @@ -84,6 +94,58 @@ fn bench_account(c: &mut Criterion) { }) }, ); + + let transactions = load_predefined_transactions(); + c.bench_with_input( + BenchmarkId::new("transactions", "ref"), + &(&transactions, &filters), + |b, (transactions, filters)| { + b.iter(|| { + for transaction in transactions.iter() { + let msg = Message { + filters: (*filters).clone(), + message: MessageRef::transaction(&MessageTransaction { + transaction: Arc::clone(transaction), + slot: 42, + }), + }; + msg.encode_to_vec().len(); + } + }) + }, + ); + c.bench_with_input( + BenchmarkId::new("transactions", "prost"), + &(&transactions, &filters), + |b, (transactions, filters)| { + b.iter(|| { + for transaction in transactions.iter() { + let msg = build_subscribe_update( + filters, + build_subscribe_transaction(transaction, 42), + ); + msg.encode_to_vec().len(); + } + }) + }, + ); + + let transactions = transactions + .into_iter() + .map(|transaction| build_subscribe_transaction(transaction.as_ref(), 42)) + .collect::>(); + c.bench_with_input( + BenchmarkId::new("transactions", "prost clone"), + &(&transactions, &filters), + |b, (transactions, filters)| { + b.iter(|| { + for transaction in transactions.iter() { + let msg = build_subscribe_update(filters, transaction.clone()); + msg.encode_to_vec().len(); + } + }) + }, + ); } criterion_group!( diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 15a44376..855a1be8 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -1559,7 +1559,7 @@ pub mod tests { filters } - fn create_account_data_slice() -> Vec { + pub fn create_account_data_slice() -> Vec { let mut data_slice1 = FilterAccountsDataSlice::new(); data_slice1.push(Range { start: 0, end: 0 }); @@ -1630,7 +1630,7 @@ pub mod tests { vec } - fn create_entries() -> Vec> { + pub fn create_entries() -> Vec> { [ MessageEntry { slot: 299888121, @@ -1654,6 +1654,80 @@ pub mod tests { .collect() } + pub fn load_predefined() -> Vec { + fs::read_dir("./src/plugin/blocks") + .expect("failed to read `blocks` dir") + .map(|entry| { + let path = entry.expect("failed to read `blocks` dir entry").path(); + let data = fs::read(path).expect("failed to read block"); + generated::ConfirmedBlock::decode(data.as_slice()) + .expect("failed to decode block") + .try_into() + .expect("failed to convert decoded block") + }) + .collect() + } + + pub fn load_predefined_blockmeta() -> Vec> { + load_predefined() + .into_iter() + .flat_map(|block| { + let slot = block.parent_slot + 1; + let block_meta1 = MessageBlockMeta { + parent_slot: block.parent_slot, + slot, + parent_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + rewards: block.rewards, + num_partitions: block.num_partitions, + block_time: block.block_time, + block_height: block.block_height, + executed_transaction_count: block.transactions.len() as u64, + entries_count: create_entries().len() as u64, + }; + + let mut block_meta2 = block_meta1.clone(); + block_meta2.num_partitions = Some(42); + + vec![block_meta1, block_meta2] + }) + .map(Arc::new) + .collect() + } + + pub fn load_predefined_transactions() -> Vec> { + load_predefined() + .into_iter() + .flat_map(|block| { + block + .transactions + .into_iter() + .enumerate() + .map(|(index, tx)| { + let TransactionWithStatusMeta::Complete(tx) = tx else { + panic!("tx with missed meta"); + }; + let transaction = SanitizedTransaction::try_create( + tx.transaction.clone(), + MessageHash::Compute, + None, + SimpleAddressLoader::Disabled, + &HashSet::new(), + ) + .expect("failed to create tx"); + MessageTransactionInfo { + signature: tx.transaction.signatures[0], + is_vote: true, + transaction, + meta: tx.meta.clone(), + index, + } + }) + }) + .map(Arc::new) + .collect() + } + fn encode_decode_cmp(filters: &[&str], message: MessageRef) { let msg = Message { filters: create_message_filters(filters), @@ -1702,6 +1776,23 @@ pub mod tests { } } + #[test] + fn test_message_transaction() { + for transaction in load_predefined_transactions() { + let msg = MessageTransaction { + transaction, + slot: 42, + }; + encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); + encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); + } + } + + #[test] + const fn test_message_block() { + // TODO + } + #[test] fn test_message_ping() { encode_decode_cmp(&["123"], MessageRef::Ping) @@ -1714,80 +1805,16 @@ pub mod tests { } #[test] - fn test_message_entry() { - for entry in create_entries() { - encode_decode_cmp(&["123"], MessageRef::entry(entry)); + fn test_message_blockmeta() { + for block_meta in load_predefined_blockmeta() { + encode_decode_cmp(&["123"], MessageRef::block_meta(block_meta)); } } #[test] - fn test_predefined() { - let location = "./src/plugin/blocks"; - for entry in fs::read_dir(location).expect("failed to read `blocks` dir") { - let path = entry.expect("failed to read `blocks` dir entry").path(); - let data = fs::read(path).expect("failed to read block"); - let block: ConfirmedBlock = generated::ConfirmedBlock::decode(data.as_slice()) - .expect("failed to decode block") - .try_into() - .expect("failed to convert decoded block"); - - let slot = block.parent_slot + 1; - let mut block_meta = MessageBlockMeta { - parent_slot: block.parent_slot, - slot, - parent_blockhash: block.previous_blockhash, - blockhash: block.blockhash, - rewards: block.rewards, - num_partitions: block.num_partitions, - block_time: block.block_time, - block_height: block.block_height, - executed_transaction_count: block.transactions.len() as u64, - entries_count: create_entries().len() as u64, - }; - - encode_decode_cmp( - &["123"], - MessageRef::block_meta(Arc::new(block_meta.clone())), - ); - - block_meta.num_partitions = Some(42); - encode_decode_cmp(&["123"], MessageRef::block_meta(Arc::new(block_meta))); - - let transactions_info = block - .transactions - .iter() - .enumerate() - .map(|(index, tx)| { - let TransactionWithStatusMeta::Complete(tx) = tx else { - panic!("tx with missed meta"); - }; - let transaction = SanitizedTransaction::try_create( - tx.transaction.clone(), - MessageHash::Compute, - None, - SimpleAddressLoader::Disabled, - &HashSet::new(), - ) - .expect("failed to create tx"); - MessageTransactionInfo { - signature: tx.transaction.signatures[0], - is_vote: true, - transaction, - meta: tx.meta.clone(), - index, - } - }) - .map(Arc::new) - .collect::>(); - - for tx in transactions_info.iter() { - let msg = MessageTransaction { - transaction: Arc::clone(tx), - slot: 42, - }; - encode_decode_cmp(&["123"], MessageRef::transaction(&msg)); - encode_decode_cmp(&["123"], MessageRef::transaction_status(&msg)); - } + fn test_message_entry() { + for entry in create_entries() { + encode_decode_cmp(&["123"], MessageRef::entry(entry)); } } } From 89f43855889fa74d6ccdc2bb4874fe15fcfbfae6 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 9 Nov 2024 22:54:04 +0200 Subject: [PATCH 37/50] impl block --- yellowstone-grpc-proto/benches/encode.rs | 54 ++++- .../src/plugin/message_ref.rs | 220 ++++++++++++++---- 2 files changed, 230 insertions(+), 44 deletions(-) diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index 9b57ece6..26f56d6e 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -11,8 +11,11 @@ use { filter::FilterAccountsDataSlice, message::{MessageAccount, MessageTransaction, MessageTransactionInfo}, message_ref::{ - tests::{create_accounts, create_message_filters, load_predefined_transactions}, - Message, MessageFilters, MessageRef, + tests::{ + create_accounts, create_message_filters, load_predefined_blocks, + load_predefined_transactions, + }, + Message, MessageFilters, MessageRef, MessageRefBlock, }, }, }, @@ -43,6 +46,10 @@ fn build_subscribe_transaction(transaction: &MessageTransactionInfo, slot: u64) }) } +fn build_subscribe_block(block: &MessageRefBlock) -> UpdateOneof { + UpdateOneof::Block(block.into()) +} + fn bench_account(c: &mut Criterion) { let filters = create_message_filters(&["my special filter"]); @@ -146,6 +153,49 @@ fn bench_account(c: &mut Criterion) { }) }, ); + + let blocks = load_predefined_blocks(); + c.bench_with_input( + BenchmarkId::new("blocks", "ref"), + &(blocks.as_slice(), &filters), + |b, (blocks, filters)| { + b.iter(|| { + for block in blocks.iter() { + let msg = Message { + filters: (*filters).clone(), + message: MessageRef::block(block.clone()), + }; + msg.encode_to_vec().len(); + } + }) + }, + ); + c.bench_with_input( + BenchmarkId::new("blocks", "prost"), + &(blocks.as_slice(), &filters), + |b, (blocks, filters)| { + b.iter(|| { + for block in blocks.iter() { + let msg = build_subscribe_update(filters, build_subscribe_block(block)); + msg.encode_to_vec().len(); + } + }) + }, + ); + + let blocks = blocks.iter().map(build_subscribe_block).collect::>(); + c.bench_with_input( + BenchmarkId::new("blocks", "prost clone"), + &(blocks.as_slice(), &filters), + |b, (blocks, filters)| { + b.iter(|| { + for block in blocks.iter() { + let msg = build_subscribe_update(filters, block.clone()); + msg.encode_to_vec().len(); + } + }) + }, + ); } criterion_group!( diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 855a1be8..d9d37cd8 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -40,7 +40,7 @@ use { solana_transaction_status::{ InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta, TransactionTokenBalance, }, - std::{borrow::Cow, ops::Range, sync::Arc}, + std::{borrow::Cow, sync::Arc}, }; #[inline] @@ -284,7 +284,7 @@ impl From<(&MessageAccountInfo, &FilterAccountsDataSlice)> for SubscribeUpdateAc impl prost::Message for MessageAccountRef { fn encode_raw(&self, buf: &mut impl BufMut) { - Self::account_encode(&self.account, &self.data_slice, 1u32, buf); + Self::account_encode_raw(1u32, &self.account, &self.data_slice, buf); if self.slot != 0u64 { ::prost::encoding::uint64::encode(2u32, &self.slot, buf); } @@ -358,10 +358,10 @@ impl MessageAccountRef { } } - fn account_encode( + fn account_encode_raw( + tag: u32, account: &MessageAccountInfo, data_slice: &FilterAccountsDataSlice, - tag: u32, buf: &mut impl BufMut, ) { encode_key(tag, WireType::LengthDelimited, buf); @@ -1168,7 +1168,7 @@ impl prost::Message for MessageTransactionStatusRef { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MessageRefBlock { pub meta: Arc, pub transactions: Vec>, @@ -1215,11 +1215,106 @@ impl From<&MessageRefBlock> for SubscribeUpdateBlock { impl prost::Message for MessageRefBlock { fn encode_raw(&self, buf: &mut impl BufMut) { - todo!() + if self.meta.slot != 0u64 { + ::prost::encoding::uint64::encode(1u32, &self.meta.slot, buf); + } + if !self.meta.blockhash.is_empty() { + ::prost::encoding::string::encode(2u32, &self.meta.blockhash, buf); + } + self.meta.rewards_encode_raw(3u32, buf); + if let Some(block_time) = self.meta.block_time { + let msg = convert_to::create_timestamp(block_time); + ::prost::encoding::message::encode(4u32, &msg, buf); + } + if let Some(block_height) = self.meta.block_height { + let msg = convert_to::create_block_height(block_height); + ::prost::encoding::message::encode(5u32, &msg, buf); + } + for tx in &self.transactions { + MessageTransactionRef::tx_meta_encode_raw(6u32, tx.as_ref(), buf); + } + if self.meta.parent_slot != 0u64 { + ::prost::encoding::uint64::encode(7u32, &self.meta.parent_slot, buf); + } + if !self.meta.parent_blockhash.is_empty() { + ::prost::encoding::string::encode(8u32, &self.meta.parent_blockhash, buf); + } + if self.meta.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encode(9u32, &self.meta.executed_transaction_count, buf); + } + if self.updated_account_count != 0u64 { + ::prost::encoding::uint64::encode(10u32, &self.updated_account_count, buf); + } + for account in &self.accounts { + MessageAccountRef::account_encode_raw( + 11u32, + account.as_ref(), + &self.accounts_data_slice, + buf, + ); + } + if self.meta.entries_count != 0u64 { + ::prost::encoding::uint64::encode(12u32, &self.meta.entries_count, buf); + } + for entry in &self.entries { + encode_key(13u32, WireType::LengthDelimited, buf); + encode_varint(entry.encoded_len() as u64, buf); + entry.encode_raw(buf); + } } fn encoded_len(&self) -> usize { - todo!() + (if self.meta.slot != 0u64 { + ::prost::encoding::uint64::encoded_len(1u32, &self.meta.slot) + } else { + 0 + }) + if !self.meta.blockhash.is_empty() { + ::prost::encoding::string::encoded_len(2u32, &self.meta.blockhash) + } else { + 0 + } + prost_field_encoded_len(3u32, self.meta.rewards_encoded_len()) + + self + .meta + .block_time + .map(convert_to::create_timestamp) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(4u32, &msg)) + + self + .meta + .block_height + .map(convert_to::create_block_height) + .map_or(0, |msg| ::prost::encoding::message::encoded_len(5u32, &msg)) + + prost_message_repeated_encoded_len!(6u32, self.transactions, |tx| { + MessageTransactionRef::tx_meta_encoded_len(tx.as_ref()) + }) + + if self.meta.parent_slot != 0u64 { + ::prost::encoding::uint64::encoded_len(7u32, &self.meta.parent_slot) + } else { + 0 + } + + if !self.meta.parent_blockhash.is_empty() { + ::prost::encoding::string::encoded_len(8u32, &self.meta.parent_blockhash) + } else { + 0 + } + + if self.meta.executed_transaction_count != 0u64 { + ::prost::encoding::uint64::encoded_len(9u32, &self.meta.executed_transaction_count) + } else { + 0 + } + + if self.updated_account_count != 0u64 { + ::prost::encoding::uint64::encoded_len(10u32, &self.updated_account_count) + } else { + 0 + } + + prost_message_repeated_encoded_len!(11u32, self.accounts, |account| { + MessageAccountRef::account_encoded_len(account.as_ref(), &self.accounts_data_slice) + }) + + if self.meta.entries_count != 0u64 { + ::prost::encoding::uint64::encoded_len(12u32, &self.meta.entries_count) + } else { + 0 + } + + prost_message_repeated_encoded_len!(13u32, self.entries, |entry| entry.encoded_len()) } fn merge_field( @@ -1548,7 +1643,12 @@ pub mod tests { }, solana_storage_proto::convert::generated, solana_transaction_status::{ConfirmedBlock, TransactionWithStatusMeta}, - std::{collections::HashSet, fs, str::FromStr}, + std::{ + collections::{HashMap, HashSet}, + fs, + ops::Range, + str::FromStr, + }, }; pub fn create_message_filters(names: &[&str]) -> MessageFilters { @@ -1578,7 +1678,7 @@ pub mod tests { ] } - pub fn create_accounts() -> Vec<(MessageAccount, FilterAccountsDataSlice)> { + pub fn create_accounts_raw() -> Vec> { let pubkey = Pubkey::from_str("28Dncoh8nmzXYEGLUcBA5SUw5WDwDBn15uUCwrWBbyuu").unwrap(); let owner = Pubkey::from_str("5jrPJWVGrFvQ2V9wRZC3kHEZhxo9pmMir15x73oHT6mn").unwrap(); let txn_signature = Signature::from_str("4V36qYhukXcLFuvhZaudSoJpPaFNB7d5RqYKjL2xiSKrxaBfEajqqL4X6viZkEvHJ8XcTJsqVjZxFegxhN7EC9V5").unwrap(); @@ -1611,9 +1711,12 @@ pub mod tests { } } } + accounts + } + pub fn create_accounts() -> Vec<(MessageAccount, FilterAccountsDataSlice)> { let mut vec = vec![]; - for account in accounts { + for account in create_accounts_raw() { for slot in [0, 42] { for is_startup in [true, false] { for data_slice in create_account_data_slice() { @@ -1669,37 +1772,28 @@ pub mod tests { } pub fn load_predefined_blockmeta() -> Vec> { - load_predefined() + load_predefined_blocks() .into_iter() - .flat_map(|block| { - let slot = block.parent_slot + 1; - let block_meta1 = MessageBlockMeta { - parent_slot: block.parent_slot, - slot, - parent_blockhash: block.previous_blockhash, - blockhash: block.blockhash, - rewards: block.rewards, - num_partitions: block.num_partitions, - block_time: block.block_time, - block_height: block.block_height, - executed_transaction_count: block.transactions.len() as u64, - entries_count: create_entries().len() as u64, - }; - - let mut block_meta2 = block_meta1.clone(); - block_meta2.num_partitions = Some(42); - - vec![block_meta1, block_meta2] - }) - .map(Arc::new) + .map(|block| (block.meta.blockhash.clone(), block.meta)) + .collect::>() + .into_values() .collect() } pub fn load_predefined_transactions() -> Vec> { + load_predefined_blocks() + .into_iter() + .flat_map(|block| block.transactions.into_iter().map(|tx| (tx.signature, tx))) + .collect::>() + .into_values() + .collect() + } + + pub fn load_predefined_blocks() -> Vec { load_predefined() .into_iter() .flat_map(|block| { - block + let transactions = block .transactions .into_iter() .enumerate() @@ -1723,8 +1817,54 @@ pub mod tests { index, } }) + .map(Arc::new) + .collect::>(); + + let entries = create_entries(); + + let slot = block.parent_slot + 1; + let block_meta1 = MessageBlockMeta { + parent_slot: block.parent_slot, + slot, + parent_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + rewards: block.rewards, + num_partitions: block.num_partitions, + block_time: block.block_time, + block_height: block.block_height, + executed_transaction_count: transactions.len() as u64, + entries_count: entries.len() as u64, + }; + let mut block_meta2 = block_meta1.clone(); + block_meta2.num_partitions = Some(42); + + let block_meta1 = Arc::new(block_meta1); + let block_meta2 = Arc::new(block_meta2); + + let accounts = create_accounts_raw(); + create_account_data_slice() + .into_iter() + .flat_map(move |data_slice| { + vec![ + MessageRefBlock { + meta: Arc::clone(&block_meta1), + transactions: transactions.clone(), + updated_account_count: accounts.len() as u64, + accounts: accounts.clone(), + accounts_data_slice: data_slice.clone(), + entries: entries.clone(), + }, + MessageRefBlock { + meta: Arc::clone(&block_meta2), + transactions: transactions.clone(), + updated_account_count: accounts.len() as u64, + accounts: accounts.clone(), + accounts_data_slice: data_slice, + entries: entries.clone(), + }, + ] + }) }) - .map(Arc::new) .collect() } @@ -1734,12 +1874,6 @@ pub mod tests { message, }; let update = SubscribeUpdate::from(&msg); - // - // let len1 = msg.encoded_len(); - // let len2 = update.encoded_len(); - // if len1 != len2 { - // println!("my {len1} vs proto {len2}"); - // } assert_eq!(msg.encoded_len(), update.encoded_len()); assert_eq!( SubscribeUpdate::decode(msg.encode_to_vec().as_slice()).expect("failed to decode"), @@ -1789,8 +1923,10 @@ pub mod tests { } #[test] - const fn test_message_block() { - // TODO + fn test_message_block() { + for block in load_predefined_blocks() { + encode_decode_cmp(&["123"], MessageRef::block(block)); + } } #[test] From 7ff349f41b81004ccf6e355401af9ce8af91fbf7 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 9 Nov 2024 23:30:40 +0200 Subject: [PATCH 38/50] fix tests --- yellowstone-grpc-geyser/src/filters.rs | 48 +++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 71739998..8b449b9a 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -1219,10 +1219,10 @@ mod tests { }, ); - let config = SubscribeRequest { + let mut config = SubscribeRequest { accounts: HashMap::new(), slots: HashMap::new(), - transactions, + transactions: transactions.clone(), transactions_status: HashMap::new(), blocks: HashMap::new(), blocks_meta: HashMap::new(), @@ -1238,7 +1238,7 @@ mod tests { create_message_transaction(&keypair_b, vec![account_key_b, account_key_a]); let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); - assert_eq!(updates.len(), 2); + assert_eq!(updates.len(), 1); assert_eq!( updates[0].filters, FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) @@ -1247,7 +1247,15 @@ mod tests { updates[0].message, FilteredMessageRef::Transaction(_) )); - assert_eq!(updates[1].filters, FilteredMessageFilters::new()); + + config.transactions_status = transactions; + let filter = Filter::new(&config, &limit, &mut create_filter_names()).unwrap(); + let updates = filter.get_filters(&message, None); + assert_eq!(updates.len(), 2); + assert_eq!( + updates[1].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[1].message, FilteredMessageRef::TransactionStatus(_) @@ -1275,10 +1283,10 @@ mod tests { }, ); - let config = SubscribeRequest { + let mut config = SubscribeRequest { accounts: HashMap::new(), slots: HashMap::new(), - transactions, + transactions: transactions.clone(), transactions_status: HashMap::new(), blocks: HashMap::new(), blocks_meta: HashMap::new(), @@ -1294,7 +1302,7 @@ mod tests { create_message_transaction(&keypair_b, vec![account_key_b, account_key_a]); let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); - assert_eq!(updates.len(), 2); + assert_eq!(updates.len(), 1); assert_eq!( updates[0].filters, FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) @@ -1303,7 +1311,15 @@ mod tests { updates[0].message, FilteredMessageRef::Transaction(_) )); - assert_eq!(updates[1].filters, FilteredMessageFilters::new()); + + config.transactions_status = transactions; + let filter = Filter::new(&config, &limit, &mut create_filter_names()).unwrap(); + let updates = filter.get_filters(&message, None); + assert_eq!(updates.len(), 2); + assert_eq!( + updates[1].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[1].message, FilteredMessageRef::TransactionStatus(_) @@ -1381,10 +1397,10 @@ mod tests { }, ); - let config = SubscribeRequest { + let mut config = SubscribeRequest { accounts: HashMap::new(), slots: HashMap::new(), - transactions, + transactions: transactions.clone(), transactions_status: HashMap::new(), blocks: HashMap::new(), blocks_meta: HashMap::new(), @@ -1402,7 +1418,7 @@ mod tests { ); let message = Message::Transaction(message_transaction); let updates = filter.get_filters(&message, None); - assert_eq!(updates.len(), 2); + assert_eq!(updates.len(), 1); assert_eq!( updates[0].filters, FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) @@ -1411,7 +1427,15 @@ mod tests { updates[0].message, FilteredMessageRef::Transaction(_) )); - assert_eq!(updates[1].filters, FilteredMessageFilters::new()); + + config.transactions_status = transactions; + let filter = Filter::new(&config, &limit, &mut create_filter_names()).unwrap(); + let updates = filter.get_filters(&message, None); + assert_eq!(updates.len(), 2); + assert_eq!( + updates[1].filters, + FilteredMessageFilters::from_vec(vec![FilterName::new("serum")]) + ); assert!(matches!( updates[1].message, FilteredMessageRef::TransactionStatus(_) From 1bd74f275d853f40f5e33220060bf4bd625d96ee Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 16:16:38 +0200 Subject: [PATCH 39/50] reduce FilteredMessages size --- yellowstone-grpc-geyser/src/filters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 8b449b9a..064b6cee 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -40,7 +40,7 @@ use { }, }; -pub type FilteredMessages = SmallVec<[FilteredMessage; 8]>; +pub type FilteredMessages = SmallVec<[FilteredMessage; 2]>; macro_rules! filtered_messages_once_owned { ($filters:ident, $message:expr) => {{ From dd3bd9235e2869e1d82bb1329c305f2a090eb046 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 16:19:08 +0200 Subject: [PATCH 40/50] rename entries --- yellowstone-grpc-geyser/src/filters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 064b6cee..b0e5b3b0 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -175,7 +175,7 @@ impl Filter { "transactions_status", self.transactions_status.filters.len(), ), - ("entry", self.entries.filters.len()), + ("entries", self.entries.filters.len()), ("blocks", self.blocks.filters.len()), ("blocks_meta", self.blocks_meta.filters.len()), ( From b465158fa52d7b4f93b5ac72340e1458f780e977 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 16:59:38 +0200 Subject: [PATCH 41/50] boxing block message --- yellowstone-grpc-geyser/src/filters.rs | 4 ++-- yellowstone-grpc-proto/benches/encode.rs | 2 +- yellowstone-grpc-proto/src/plugin/message_ref.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index b0e5b3b0..581ae9ba 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -927,14 +927,14 @@ impl FilterBlocks { message_filters.push(filter.clone()); messages.push(FilteredMessage::new( message_filters, - FilteredMessageRef::block(FilteredMessageRefBlock { + FilteredMessageRef::block(Box::new(FilteredMessageRefBlock { meta: Arc::clone(&message.meta), transactions, updated_account_count: message.updated_account_count, accounts_data_slice: accounts_data_slice.clone(), accounts, entries, - }), + })), )); } messages diff --git a/yellowstone-grpc-proto/benches/encode.rs b/yellowstone-grpc-proto/benches/encode.rs index 26f56d6e..deb66586 100644 --- a/yellowstone-grpc-proto/benches/encode.rs +++ b/yellowstone-grpc-proto/benches/encode.rs @@ -163,7 +163,7 @@ fn bench_account(c: &mut Criterion) { for block in blocks.iter() { let msg = Message { filters: (*filters).clone(), - message: MessageRef::block(block.clone()), + message: MessageRef::block(Box::new(block.clone())), }; msg.encode_to_vec().len(); } diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index d9d37cd8..3a43a7b7 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -134,7 +134,7 @@ pub enum MessageRef { Slot(MessageSlot), // 3 Transaction(MessageTransactionRef), // 4 TransactionStatus(MessageTransactionStatusRef), // 10 - Block(MessageRefBlock), // 5 + Block(Box), // 5 Ping, // 6 Pong(MessageRefPong), // 9 BlockMeta(Arc), // 7 @@ -148,7 +148,7 @@ impl From<&MessageRef> for UpdateOneof { MessageRef::Slot(msg) => Self::Slot(msg.into()), MessageRef::Transaction(msg) => Self::Transaction(msg.into()), MessageRef::TransactionStatus(msg) => Self::TransactionStatus(msg.into()), - MessageRef::Block(msg) => Self::Block(msg.into()), + MessageRef::Block(msg) => Self::Block(msg.as_ref().into()), MessageRef::Ping => Self::Ping(SubscribeUpdatePing {}), MessageRef::Pong(msg) => Self::Pong(SubscribeUpdatePong { id: msg.id }), MessageRef::BlockMeta(msg) => Self::BlockMeta(msg.as_ref().into()), @@ -232,7 +232,7 @@ impl MessageRef { }) } - pub const fn block(message: MessageRefBlock) -> Self { + pub const fn block(message: Box) -> Self { Self::Block(message) } @@ -1925,7 +1925,7 @@ pub mod tests { #[test] fn test_message_block() { for block in load_predefined_blocks() { - encode_decode_cmp(&["123"], MessageRef::block(block)); + encode_decode_cmp(&["123"], MessageRef::block(Box::new(block))); } } From e66def34b7419ce30b2c5402ff7678b3d59586f7 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 17:33:38 +0200 Subject: [PATCH 42/50] arced slices --- yellowstone-grpc-geyser/src/filters.rs | 4 +-- yellowstone-grpc-proto/src/plugin/filter.rs | 31 +++++++++++++------ .../src/plugin/message_ref.rs | 25 ++++++--------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 581ae9ba..320ea96c 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -984,7 +984,7 @@ pub fn parse_accounts_data_slice_create( start: s.offset as usize, end: (s.offset + s.length) as usize, }) - .collect::(); + .collect::>(); for (i, slice_a) in slices.iter().enumerate() { // check order @@ -998,7 +998,7 @@ pub fn parse_accounts_data_slice_create( } } - Ok(slices) + Ok(FilterAccountsDataSlice::new(slices)) } #[cfg(test)] diff --git a/yellowstone-grpc-proto/src/plugin/filter.rs b/yellowstone-grpc-proto/src/plugin/filter.rs index 90450942..c9c4235e 100644 --- a/yellowstone-grpc-proto/src/plugin/filter.rs +++ b/yellowstone-grpc-proto/src/plugin/filter.rs @@ -1,12 +1,9 @@ -use { - smallvec::SmallVec, - std::{ - borrow::Borrow, - collections::HashSet, - ops::Range, - sync::Arc, - time::{Duration, Instant}, - }, +use std::{ + borrow::Borrow, + collections::HashSet, + ops::Range, + sync::Arc, + time::{Duration, Instant}, }; #[derive(Debug, thiserror::Error)] @@ -96,4 +93,18 @@ impl FilterNames { } } -pub type FilterAccountsDataSlice = SmallVec<[Range; 4]>; +#[derive(Debug, Clone, Default)] +pub struct FilterAccountsDataSlice(Arc>>); + +impl AsRef<[Range]> for FilterAccountsDataSlice { + #[inline] + fn as_ref(&self) -> &[Range] { + &self.0 + } +} + +impl FilterAccountsDataSlice { + pub fn new(slices: Vec>) -> Self { + Self(Arc::new(slices)) + } +} diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 3a43a7b7..520126dd 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -328,6 +328,7 @@ impl MessageAccountRef { account: &'a MessageAccountInfo, data_slice: &'a FilterAccountsDataSlice, ) -> Cow<'a, Vec> { + let data_slice = data_slice.as_ref(); if data_slice.is_empty() { Cow::Borrowed(&account.data) } else { @@ -345,6 +346,7 @@ impl MessageAccountRef { account: &MessageAccountInfo, data_slice: &FilterAccountsDataSlice, ) -> usize { + let data_slice = data_slice.as_ref(); if data_slice.is_empty() { account.data.len() } else { @@ -1660,22 +1662,15 @@ pub mod tests { } pub fn create_account_data_slice() -> Vec { - let mut data_slice1 = FilterAccountsDataSlice::new(); - data_slice1.push(Range { start: 0, end: 0 }); - - let mut data_slice2 = FilterAccountsDataSlice::new(); - data_slice2.push(Range { start: 2, end: 3 }); - - let mut data_slice3 = FilterAccountsDataSlice::new(); - data_slice3.push(Range { start: 1, end: 3 }); - data_slice3.push(Range { start: 5, end: 10 }); - - vec![ - FilterAccountsDataSlice::new(), - data_slice1, - data_slice2, - data_slice3, + [ + vec![], + vec![Range { start: 0, end: 0 }], + vec![Range { start: 2, end: 3 }], + vec![Range { start: 1, end: 3 }, Range { start: 5, end: 10 }], ] + .into_iter() + .map(FilterAccountsDataSlice::new) + .collect() } pub fn create_accounts_raw() -> Vec> { From 883adedebb3930c90bc0eabcf891af515bd1d2ce Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 18:02:03 +0200 Subject: [PATCH 43/50] add editorconfig --- .editorconfig | 22 ++++ yellowstone-grpc-geyser/config.json | 164 +++++++++++++--------------- 2 files changed, 100 insertions(+), 86 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6dddb172 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{diff,md}] +trim_trailing_whitespace = false + +[*.{js,json}] +indent_style = space +indent_size = 2 + +[*.proto] +indent_style = space +indent_size = 2 + +[*.rs] +indent_style = space +indent_size = 4 diff --git a/yellowstone-grpc-geyser/config.json b/yellowstone-grpc-geyser/config.json index 921ad261..7a23fa0e 100644 --- a/yellowstone-grpc-geyser/config.json +++ b/yellowstone-grpc-geyser/config.json @@ -1,90 +1,82 @@ { - "libpath": "../target/debug/libyellowstone_grpc_geyser.so", - "log": { - "level": "info" + "libpath": "../target/debug/libyellowstone_grpc_geyser.so", + "log": { + "level": "info" + }, + "grpc": { + "address": "0.0.0.0:10000", + "tls_config": { + "cert_path": "", + "key_path": "" }, - "grpc": { - "address": "0.0.0.0:10000", - "tls_config": { - "cert_path": "", - "key_path": "" - }, - "compression": { - "accept": [ - "gzip" - ], - "send": [ - "gzip" - ] - }, - "max_decoding_message_size": "4_194_304", - "snapshot_plugin_channel_capacity": null, - "snapshot_client_channel_capacity": "50_000_000", - "channel_capacity": "100_000", - "unary_concurrency_limit": 100, - "unary_disabled": false, - "x_token": null, - "filter_name_size_limit": 32, - "filter_names_size_limit": 1024, - "filter_names_cleanup_interval": "1s", - "filters": { - "accounts": { - "max": 1, - "any": false, - "account_max": 10, - "account_reject": [ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ], - "owner_max": 10, - "owner_reject": [ - "11111111111111111111111111111111" - ], - "data_slice_max": 2 - }, - "slots": { - "max": 1 - }, - "transactions": { - "max": 1, - "any": false, - "account_include_max": 10, - "account_include_reject": [ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ], - "account_exclude_max": 10, - "account_required_max": 10 - }, - "transactions_status": { - "max": 1, - "any": false, - "account_include_max": 10, - "account_include_reject": [ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ], - "account_exclude_max": 10, - "account_required_max": 10 - }, - "blocks": { - "max": 1, - "account_include_max": 10, - "account_include_any": false, - "account_include_reject": [ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - ], - "include_transactions": true, - "include_accounts": false, - "include_entries": false - }, - "blocks_meta": { - "max": 1 - }, - "entry": { - "max": 1 - } - } + "compression": { + "accept": ["gzip"], + "send": ["gzip"] }, - "prometheus": { - "address": "0.0.0.0:8999" - }, - "block_fail_action": "log" + "max_decoding_message_size": "4_194_304", + "snapshot_plugin_channel_capacity": null, + "snapshot_client_channel_capacity": "50_000_000", + "channel_capacity": "100_000", + "unary_concurrency_limit": 100, + "unary_disabled": false, + "x_token": null, + "filter_name_size_limit": 32, + "filter_names_size_limit": 1024, + "filter_names_cleanup_interval": "1s", + "filters": { + "accounts": { + "max": 1, + "any": false, + "account_max": 10, + "account_reject": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], + "owner_max": 10, + "owner_reject": ["11111111111111111111111111111111"], + "data_slice_max": 2 + }, + "slots": { + "max": 1 + }, + "transactions": { + "max": 1, + "any": false, + "account_include_max": 10, + "account_include_reject": [ + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "account_exclude_max": 10, + "account_required_max": 10 + }, + "transactions_status": { + "max": 1, + "any": false, + "account_include_max": 10, + "account_include_reject": [ + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "account_exclude_max": 10, + "account_required_max": 10 + }, + "blocks": { + "max": 1, + "account_include_max": 10, + "account_include_any": false, + "account_include_reject": [ + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "include_transactions": true, + "include_accounts": false, + "include_entries": false + }, + "blocks_meta": { + "max": 1 + }, + "entry": { + "max": 1 + } + } + }, + "prometheus": { + "address": "0.0.0.0:8999" + }, + "block_fail_action": "log" } From 8ebf128fff88bba1cea5a3bb856fd7a7090519f7 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 22:54:48 +0200 Subject: [PATCH 44/50] add pretty print per message in the client --- examples/rust/src/bin/client.rs | 308 ++++++++++++++++-------------- yellowstone-grpc-proto/src/lib.rs | 15 +- 2 files changed, 179 insertions(+), 144 deletions(-) diff --git a/examples/rust/src/bin/client.rs b/examples/rust/src/bin/client.rs index 286e07e2..a8b55832 100644 --- a/examples/rust/src/bin/client.rs +++ b/examples/rust/src/bin/client.rs @@ -1,25 +1,31 @@ use { + anyhow::Context, backoff::{future::retry, ExponentialBackoff}, clap::{Parser, Subcommand, ValueEnum}, futures::{future::TryFutureExt, sink::SinkExt, stream::StreamExt}, log::{error, info}, - solana_sdk::{pubkey::Pubkey, signature::Signature, transaction::TransactionError}, - solana_transaction_status::{EncodedTransactionWithStatusMeta, UiTransactionEncoding}, - std::{collections::HashMap, env, fmt, fs::File, sync::Arc, time::Duration}, + serde_json::{json, Value}, + solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature}, + solana_transaction_status::UiTransactionEncoding, + std::{collections::HashMap, env, fs::File, sync::Arc, time::Duration}, tokio::sync::Mutex, tonic::transport::channel::ClientTlsConfig, yellowstone_grpc_client::{GeyserGrpcClient, GeyserGrpcClientError, Interceptor}, - yellowstone_grpc_proto::prelude::{ - subscribe_request_filter_accounts_filter::Filter as AccountsFilterOneof, - subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, - subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, - subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest, - SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, - SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, - SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks, - SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, - SubscribeRequestFilterTransactions, SubscribeRequestPing, SubscribeUpdateAccount, - SubscribeUpdateTransaction, SubscribeUpdateTransactionStatus, + yellowstone_grpc_proto::{ + convert_from, + prelude::{ + subscribe_request_filter_accounts_filter::Filter as AccountsFilterOneof, + subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, + subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof, + subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest, + SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, + SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, + SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks, + SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, + SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeRequestPing, + SubscribeUpdate, SubscribeUpdateAccountInfo, SubscribeUpdateEntry, + SubscribeUpdateTransactionInfo, + }, }, }; @@ -239,7 +245,7 @@ struct ActionSubscribe { #[clap(long)] ping: Option, - // Resubscribe (only to slots) after + /// Resubscribe (only to slots) after #[clap(long)] resub: Option, } @@ -429,110 +435,6 @@ impl Action { } } -#[derive(Debug)] -#[allow(dead_code)] -pub struct AccountPretty { - is_startup: bool, - slot: u64, - pubkey: Pubkey, - lamports: u64, - owner: Pubkey, - executable: bool, - rent_epoch: u64, - data: String, - write_version: u64, - txn_signature: String, -} - -impl From for AccountPretty { - fn from( - SubscribeUpdateAccount { - is_startup, - slot, - account, - }: SubscribeUpdateAccount, - ) -> Self { - let account = account.expect("should be defined"); - Self { - is_startup, - slot, - pubkey: Pubkey::try_from(account.pubkey).expect("valid pubkey"), - lamports: account.lamports, - owner: Pubkey::try_from(account.owner).expect("valid pubkey"), - executable: account.executable, - rent_epoch: account.rent_epoch, - data: hex::encode(account.data), - write_version: account.write_version, - txn_signature: bs58::encode(account.txn_signature.unwrap_or_default()).into_string(), - } - } -} - -#[allow(dead_code)] -pub struct TransactionPretty { - slot: u64, - signature: Signature, - is_vote: bool, - tx: EncodedTransactionWithStatusMeta, -} - -impl fmt::Debug for TransactionPretty { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - struct TxWrap<'a>(&'a EncodedTransactionWithStatusMeta); - impl<'a> fmt::Debug for TxWrap<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let serialized = serde_json::to_string(self.0).expect("failed to serialize"); - fmt::Display::fmt(&serialized, f) - } - } - - f.debug_struct("TransactionPretty") - .field("slot", &self.slot) - .field("signature", &self.signature) - .field("is_vote", &self.is_vote) - .field("tx", &TxWrap(&self.tx)) - .finish() - } -} - -impl From for TransactionPretty { - fn from(SubscribeUpdateTransaction { transaction, slot }: SubscribeUpdateTransaction) -> Self { - let tx = transaction.expect("should be defined"); - Self { - slot, - signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"), - is_vote: tx.is_vote, - tx: yellowstone_grpc_proto::convert_from::create_tx_with_meta(tx) - .expect("valid tx with meta") - .encode(UiTransactionEncoding::Base64, Some(u8::MAX), true) - .expect("failed to encode"), - } - } -} - -#[allow(dead_code)] -#[derive(Debug)] -pub struct TransactionStatusPretty { - slot: u64, - signature: Signature, - is_vote: bool, - index: u64, - err: Option, -} - -impl From for TransactionStatusPretty { - fn from(status: SubscribeUpdateTransactionStatus) -> Self { - Self { - slot: status.slot, - signature: Signature::try_from(status.signature.as_slice()).expect("valid signature"), - is_vote: status.is_vote, - index: status.index, - err: yellowstone_grpc_proto::convert_from::create_tx_error(status.err.as_ref()) - .expect("valid tx err"), - } - } -} - #[tokio::main] async fn main() -> anyhow::Result<()> { env::set_var( @@ -577,7 +479,9 @@ async fn main() -> anyhow::Result<()> { .get_subscribe_request(commitment) .await .map_err(backoff::Error::Permanent)? - .expect("expect subscribe action"); + .ok_or(backoff::Error::Permanent(anyhow::anyhow!( + "expect subscribe action" + )))?; geyser_subscribe(client, request, resub).await } @@ -643,31 +547,104 @@ async fn geyser_subscribe( let mut counter = 0; while let Some(message) = stream.next().await { match message { - Ok(msg) => { - match msg.update_oneof { - Some(UpdateOneof::Account(account)) => { - let account: AccountPretty = account.into(); - info!( - "new account update: filters {:?}, account: {:#?}", - msg.filters, account + Ok(SubscribeUpdate { + filters, + update_oneof, + }) => { + match update_oneof { + Some(UpdateOneof::Account(msg)) => { + let account = msg + .account + .ok_or(anyhow::anyhow!("no account in the message"))?; + let mut value = create_pretty_account(account)?; + value["isStartup"] = json!(msg.is_startup); + value["slot"] = json!(msg.slot); + print_update("account", &filters, value); + } + Some(UpdateOneof::Slot(msg)) => { + let status = CommitmentLevel::try_from(msg.status) + .context("failed to decode commitment")?; + print_update( + "slot", + &filters, + json!({ + "slot": msg.slot, + "parent": msg.parent, + "status": status.as_str_name() + }), ); - continue; } - Some(UpdateOneof::Transaction(tx)) => { - let tx: TransactionPretty = tx.into(); - info!( - "new transaction update: filters {:?}, transaction: {:#?}", - msg.filters, tx + Some(UpdateOneof::Transaction(msg)) => { + let tx = msg + .transaction + .ok_or(anyhow::anyhow!("no transaction in the message"))?; + let mut value = create_pretty_transaction(tx)?; + value["slot"] = json!(msg.slot); + print_update("transaction", &filters, value); + } + Some(UpdateOneof::TransactionStatus(msg)) => { + print_update( + "transactionStatus", + &filters, + json!({ + "slot": msg.slot, + "signature": Signature::try_from(msg.signature.as_slice()).context("invalid signature")?.to_string(), + "isVote": msg.is_vote, + "index": msg.index, + "err": convert_from::create_tx_error(msg.err.as_ref()) + .map_err(|error| anyhow::anyhow!(error)) + .context("invalid error")?, + }), ); - continue; } - Some(UpdateOneof::TransactionStatus(status)) => { - let status: TransactionStatusPretty = status.into(); - info!( - "new transaction update: filters {:?}, transaction status: {:?}", - msg.filters, status + Some(UpdateOneof::Entry(msg)) => { + print_update("entry", &filters, create_pretty_entry(msg)?); + } + Some(UpdateOneof::BlockMeta(msg)) => { + print_update( + "blockmeta", + &filters, + json!({ + "slot": msg.slot, + "blockhash": msg.blockhash, + "rewards": if let Some(rewards) = msg.rewards { + Some(convert_from::create_rewards_obj(rewards).map_err(|error| anyhow::anyhow!(error))?) + } else { + None + }, + "blockTime": msg.block_time.map(|obj| obj.timestamp), + "blockHeight": msg.block_height.map(|obj| obj.block_height), + "parentSlot": msg.parent_slot, + "parentBlockhash": msg.parent_blockhash, + "executedTransactionCount": msg.executed_transaction_count, + "entriesCount": msg.entries_count, + }), + ); + } + Some(UpdateOneof::Block(msg)) => { + print_update( + "block", + &filters, + json!({ + "slot": msg.slot, + "blockhash": msg.blockhash, + "rewards": if let Some(rewards) = msg.rewards { + Some(convert_from::create_rewards_obj(rewards).map_err(|error| anyhow::anyhow!(error))?) + } else { + None + }, + "blockTime": msg.block_time.map(|obj| obj.timestamp), + "blockHeight": msg.block_height.map(|obj| obj.block_height), + "parentSlot": msg.parent_slot, + "parentBlockhash": msg.parent_blockhash, + "executedTransactionCount": msg.executed_transaction_count, + "transactions": msg.transactions.into_iter().map(create_pretty_transaction).collect::>()?, + "updatedAccountCount": msg.updated_account_count, + "accounts": msg.accounts.into_iter().map(create_pretty_account).collect::>()?, + "entriesCount": msg.entries_count, + "entries": msg.entries.into_iter().map(create_pretty_entry).collect::>()?, + }), ); - continue; } Some(UpdateOneof::Ping(_)) => { // This is necessary to keep load balancers that expect client pings alive. If your load balancer doesn't @@ -679,9 +656,12 @@ async fn geyser_subscribe( }) .await?; } - _ => {} + Some(UpdateOneof::Pong(_)) => {} + None => { + error!("update not found in the message"); + break; + } } - info!("new message: {msg:?}") } Err(error) => { error!("error: {error:?}"); @@ -715,3 +695,47 @@ async fn geyser_subscribe( info!("stream closed"); Ok(()) } + +fn create_pretty_account(account: SubscribeUpdateAccountInfo) -> anyhow::Result { + Ok(json!({ + "pubkey": Pubkey::try_from(account.pubkey).map_err(|_| anyhow::anyhow!("invalid account pubkey"))?.to_string(), + "lamports": account.lamports, + "owner": Pubkey::try_from(account.owner).map_err(|_| anyhow::anyhow!("invalid account owner"))?.to_string(), + "executable": account.executable, + "rentEpoch": account.rent_epoch, + "data": hex::encode(account.data), + "writeVersion": account.write_version, + "txnSignature": account.txn_signature.map(|sig| bs58::encode(sig).into_string()), + })) +} + +fn create_pretty_transaction(tx: SubscribeUpdateTransactionInfo) -> anyhow::Result { + Ok(json!({ + "signature": Signature::try_from(tx.signature.as_slice()).context("invalid signature")?.to_string(), + "isVote": tx.is_vote, + "tx": convert_from::create_tx_with_meta(tx) + .map_err(|error| anyhow::anyhow!(error)) + .context("invalid tx with meta")? + .encode(UiTransactionEncoding::Base64, Some(u8::MAX), true) + .context("failed to encode transaction")?, + })) +} + +fn create_pretty_entry(msg: SubscribeUpdateEntry) -> anyhow::Result { + Ok(json!({ + "slot": msg.slot, + "index": msg.index, + "numHashes": msg.num_hashes, + "hash": Hash::new_from_array(<[u8; 32]>::try_from(msg.hash.as_slice()).context("invalid entry hash")?).to_string(), + "executedTransactionCount": msg.executed_transaction_count, + "startingTransactionIndex": msg.starting_transaction_index, + })) +} + +fn print_update(kind: &str, filters: &[String], value: Value) { + info!( + "{kind} ({}): {}", + filters.join(","), + serde_json::to_string(&value).expect("json serialization failed") + ); +} diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index b7c14974..c8e08a4e 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -303,8 +303,8 @@ pub mod convert_from { }, solana_transaction_status::{ ConfirmedBlock, InnerInstruction, InnerInstructions, Reward, RewardType, - TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, - VersionedTransactionWithStatusMeta, + RewardsAndNumPartitions, TransactionStatusMeta, TransactionTokenBalance, + TransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, }; @@ -530,6 +530,17 @@ pub mod convert_from { }) } + pub fn create_rewards_obj(rewards: proto::Rewards) -> Result { + Ok(RewardsAndNumPartitions { + rewards: rewards + .rewards + .into_iter() + .map(create_reward) + .collect::>()?, + num_partitions: rewards.num_partitions.map(|wrapper| wrapper.num_partitions), + }) + } + pub fn create_reward(reward: proto::Reward) -> Result { Ok(Reward { pubkey: reward.pubkey, From 7b94cf8e762889f68855ea310262c6e1745937dd Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 13 Nov 2024 23:15:49 +0200 Subject: [PATCH 45/50] fix smallvec fill --- yellowstone-grpc-geyser/src/filters.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yellowstone-grpc-geyser/src/filters.rs b/yellowstone-grpc-geyser/src/filters.rs index 320ea96c..8947f285 100644 --- a/yellowstone-grpc-geyser/src/filters.rs +++ b/yellowstone-grpc-geyser/src/filters.rs @@ -57,7 +57,9 @@ macro_rules! filtered_messages_once_ref { let mut messages = FilteredMessages::new(); if !$filters.is_empty() { let mut message_filters = FilteredMessageFilters::new(); - message_filters.clone_from_slice($filters); + for filter in $filters { + message_filters.push(filter.clone()); + } messages.push(FilteredMessage::new(message_filters, $message)); } messages From cbacf0455b188c605d57edf523d9e33e444c5b68 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 14 Nov 2024 10:37:56 +0200 Subject: [PATCH 46/50] add indicatif to client --- Cargo.lock | 67 ++++++++++++++++++++ Cargo.toml | 1 + examples/rust/Cargo.toml | 1 + examples/rust/src/bin/client.rs | 105 +++++++++++++++++++++++++++----- 4 files changed, 159 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbde6336..87c1c970 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -862,6 +862,19 @@ dependencies = [ "unreachable", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width 0.1.14", + "windows-sys 0.52.0", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1200,6 +1213,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1911,6 +1930,19 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "indicatif" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.0", + "web-time", +] + [[package]] name = "inout" version = "0.1.3" @@ -2305,6 +2337,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.36.3" @@ -2485,6 +2523,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -4341,6 +4385,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "universal-hash" version = "0.5.1" @@ -4538,6 +4594,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" @@ -4799,6 +4865,7 @@ dependencies = [ "env_logger 0.11.5", "futures", "hex", + "indicatif", "log", "maplit", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 26b7c4bf..5eb8514f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ http-body-util = "0.1.2" humantime-serde = "1.1.1" hyper = "1.4.1" hyper-util = "0.1.7" +indicatif = "0.17.9" lazy_static = "1.4.0" local-ip-address = "0.6.1" log = "0.4.17" diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index cde01bff..ee0ca7b9 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -22,6 +22,7 @@ clap = { workspace = true, features = ["derive"] } env_logger = { workspace = true } futures = { workspace = true } hex = { workspace = true } +indicatif = { workspace = true } log = { workspace = true } maplit = { workspace = true } serde_json = { workspace = true } diff --git a/examples/rust/src/bin/client.rs b/examples/rust/src/bin/client.rs index a8b55832..66fba94a 100644 --- a/examples/rust/src/bin/client.rs +++ b/examples/rust/src/bin/client.rs @@ -3,6 +3,7 @@ use { backoff::{future::retry, ExponentialBackoff}, clap::{Parser, Subcommand, ValueEnum}, futures::{future::TryFutureExt, sink::SinkExt, stream::StreamExt}, + indicatif::{MultiProgress, ProgressBar, ProgressStyle}, log::{error, info}, serde_json::{json, Value}, solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature}, @@ -23,9 +24,9 @@ use { SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeRequestPing, - SubscribeUpdate, SubscribeUpdateAccountInfo, SubscribeUpdateEntry, - SubscribeUpdateTransactionInfo, + SubscribeUpdateAccountInfo, SubscribeUpdateEntry, SubscribeUpdateTransactionInfo, }, + prost::Message, }, }; @@ -215,7 +216,7 @@ struct ActionSubscribe { transactions_status_account_required: Vec, #[clap(long)] - entry: bool, + entries: bool, /// Subscribe on block updates #[clap(long)] @@ -248,13 +249,17 @@ struct ActionSubscribe { /// Resubscribe (only to slots) after #[clap(long)] resub: Option, + + /// Show total stat instead of messages + #[clap(long, default_value_t = false)] + stats: bool, } impl Action { async fn get_subscribe_request( &self, commitment: Option, - ) -> anyhow::Result> { + ) -> anyhow::Result> { Ok(match self { Self::Subscribe(args) => { let mut accounts: AccountFilterMap = HashMap::new(); @@ -375,9 +380,9 @@ impl Action { ); } - let mut entry: EntryFilterMap = HashMap::new(); - if args.entry { - entry.insert("client".to_owned(), SubscribeRequestFilterEntry {}); + let mut entries: EntryFilterMap = HashMap::new(); + if args.entries { + entries.insert("client".to_owned(), SubscribeRequestFilterEntry {}); } let mut blocks: BlocksFilterMap = HashMap::new(); @@ -420,7 +425,7 @@ impl Action { accounts, transactions, transactions_status, - entry, + entry: entries, blocks, blocks_meta, commitment: commitment.map(|x| x as i32), @@ -428,6 +433,7 @@ impl Action { ping, }, args.resub.unwrap_or(0), + args.stats, )) } _ => None, @@ -474,7 +480,7 @@ async fn main() -> anyhow::Result<()> { .map(|response| info!("response: {response:?}")), Action::HealthWatch => geyser_health_watch(client).await, Action::Subscribe(_) => { - let (request, resub) = args + let (request, resub, stats) = args .action .get_subscribe_request(commitment) .await @@ -483,7 +489,7 @@ async fn main() -> anyhow::Result<()> { "expect subscribe action" )))?; - geyser_subscribe(client, request, resub).await + geyser_subscribe(client, request, resub, stats).await } Action::Ping { count } => client .ping(*count) @@ -540,18 +546,63 @@ async fn geyser_subscribe( mut client: GeyserGrpcClient, request: SubscribeRequest, resub: usize, + stats: bool, ) -> anyhow::Result<()> { + let pb_multi = MultiProgress::new(); + let mut pb_accounts_c = 0; + let pb_accounts = crate_progress_bar(&pb_multi, "accounts", false)?; + let mut pb_slots_c = 0; + let pb_slots = crate_progress_bar(&pb_multi, "slots", false)?; + let mut pb_txs_c = 0; + let pb_txs = crate_progress_bar(&pb_multi, "transactions", false)?; + let mut pb_txs_st_c = 0; + let pb_txs_st = crate_progress_bar(&pb_multi, "transactions statuses", false)?; + let mut pb_entries_c = 0; + let pb_entries = crate_progress_bar(&pb_multi, "entries", false)?; + let mut pb_blocks_mt_c = 0; + let pb_blocks_mt = crate_progress_bar(&pb_multi, "blocks meta", false)?; + let mut pb_blocks_c = 0; + let pb_blocks = crate_progress_bar(&pb_multi, "blocks", false)?; + let mut pb_pp_c = 0; + let pb_pp = crate_progress_bar(&pb_multi, "ping/pong", false)?; + let mut pb_total_c = 0; + let pb_total = crate_progress_bar(&pb_multi, "total", true)?; + let (mut subscribe_tx, mut stream) = client.subscribe_with_request(Some(request)).await?; info!("stream opened"); let mut counter = 0; while let Some(message) = stream.next().await { match message { - Ok(SubscribeUpdate { - filters, - update_oneof, - }) => { - match update_oneof { + Ok(msg) => { + if stats { + let encoded_len = msg.encoded_len() as u64; + let (pb_c, pb) = match msg.update_oneof { + Some(UpdateOneof::Account(_)) => (&mut pb_accounts_c, &pb_accounts), + Some(UpdateOneof::Slot(_)) => (&mut pb_slots_c, &pb_slots), + Some(UpdateOneof::Transaction(_)) => (&mut pb_txs_c, &pb_txs), + Some(UpdateOneof::TransactionStatus(_)) => (&mut pb_txs_st_c, &pb_txs_st), + Some(UpdateOneof::Entry(_)) => (&mut pb_entries_c, &pb_entries), + Some(UpdateOneof::BlockMeta(_)) => (&mut pb_blocks_mt_c, &pb_blocks_mt), + Some(UpdateOneof::Block(_)) => (&mut pb_blocks_c, &pb_blocks), + Some(UpdateOneof::Ping(_)) => (&mut pb_pp_c, &pb_pp), + Some(UpdateOneof::Pong(_)) => (&mut pb_pp_c, &pb_pp), + None => { + error!("update not found in the message"); + break; + } + }; + *pb_c += 1; + pb.set_message(format_thousands(*pb_c)); + pb.inc(encoded_len); + pb_total_c += 1; + pb_total.set_message(format_thousands(pb_total_c)); + pb_total.inc(encoded_len); + continue; + } + + let filters = msg.filters; + match msg.update_oneof { Some(UpdateOneof::Account(msg)) => { let account = msg .account @@ -696,6 +747,30 @@ async fn geyser_subscribe( Ok(()) } +fn crate_progress_bar( + pb: &MultiProgress, + kind: &str, + elapsed: bool, +) -> Result { + let pb = pb.add(ProgressBar::no_length()); + let elapsed = if elapsed { " in {elapsed_precise}" } else { "" }; + let tpl = format!("{{spinner}} {kind}: {{msg}} / ~{{bytes}} (~{{bytes_per_sec}}){elapsed}"); + pb.set_style(ProgressStyle::with_template(&tpl)?); + Ok(pb) +} + +fn format_thousands(value: u64) -> String { + value + .to_string() + .as_bytes() + .rchunks(3) + .rev() + .map(std::str::from_utf8) + .collect::, _>>() + .expect("invalid number") + .join(",") +} + fn create_pretty_account(account: SubscribeUpdateAccountInfo) -> anyhow::Result { Ok(json!({ "pubkey": Pubkey::try_from(account.pubkey).map_err(|_| anyhow::anyhow!("invalid account pubkey"))?.to_string(), From 784077ea16d437c95d57a9ddda35070bcf056d20 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Thu, 14 Nov 2024 11:34:07 +0200 Subject: [PATCH 47/50] remove str instead of String as err msg --- yellowstone-grpc-proto/src/lib.rs | 187 +++++++++++++++--------------- 1 file changed, 92 insertions(+), 95 deletions(-) diff --git a/yellowstone-grpc-proto/src/lib.rs b/yellowstone-grpc-proto/src/lib.rs index c8e08a4e..f3bb8cea 100644 --- a/yellowstone-grpc-proto/src/lib.rs +++ b/yellowstone-grpc-proto/src/lib.rs @@ -308,20 +308,15 @@ pub mod convert_from { }, }; - fn ensure_some(maybe_value: Option, message: impl Into) -> Result { - match maybe_value { - Some(value) => Ok(value), - None => Err(message.into()), - } - } + type CreateResult = Result; - pub fn create_block(block: proto::SubscribeUpdateBlock) -> Result { + pub fn create_block(block: proto::SubscribeUpdateBlock) -> CreateResult { let mut transactions = vec![]; for tx in block.transactions { transactions.push(create_tx_with_meta(tx)?); } - let block_rewards = ensure_some(block.rewards, "failed to get rewards")?; + let block_rewards = block.rewards.ok_or("failed to get rewards")?; let mut rewards = vec![]; for reward in block_rewards.rewards { rewards.push(create_reward(reward)?); @@ -334,22 +329,28 @@ pub mod convert_from { transactions, rewards, num_partitions: block_rewards.num_partitions.map(|msg| msg.num_partitions), - 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", - )?), + block_time: Some( + block + .block_time + .map(|wrapper| wrapper.timestamp) + .ok_or("failed to get block_time")?, + ), + block_height: Some( + block + .block_height + .map(|wrapper| wrapper.block_height) + .ok_or("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")?; + ) -> CreateResult { + let meta = tx.meta.ok_or("failed to get transaction meta")?; + let tx = tx + .transaction + .ok_or("failed to get transaction transaction")?; Ok(TransactionWithStatusMeta::Complete( VersionedTransactionWithStatusMeta { @@ -359,50 +360,48 @@ pub mod convert_from { )) } - pub fn create_tx_versioned(tx: proto::Transaction) -> Result { + pub fn create_tx_versioned(tx: proto::Transaction) -> CreateResult { 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()), + Err(_error) => return Err("failed to parse Signature"), }); } Ok(VersionedTransaction { signatures, - message: create_message(ensure_some(tx.message, "failed to get message")?)?, + message: create_message(tx.message.ok_or("failed to get message")?)?, }) } - pub fn create_message(message: proto::Message) -> Result { - let header = ensure_some(message.header, "failed to get MessageHeader")?; + pub fn create_message(message: proto::Message) -> CreateResult { + let header = message.header.ok_or("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", - )?, + num_required_signatures: header + .num_required_signatures + .try_into() + .map_err(|_| "failed to parse num_required_signatures")?, + num_readonly_signed_accounts: header + .num_readonly_signed_accounts + .try_into() + .map_err(|_| "failed to parse num_readonly_signed_accounts")?, + num_readonly_unsigned_accounts: header + .num_readonly_unsigned_accounts + .try_into() + .map_err(|_| "failed to parse num_readonly_unsigned_accounts")?, }; if message.recent_blockhash.len() != HASH_BYTES { - return Err("failed to parse hash".to_owned()); + return Err("failed to parse hash"); } 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", - )?, + account_key: Pubkey::try_from(table.account_key.as_slice()) + .map_err(|_| "failed to parse Pubkey")?, writable_indexes: table.writable_indexes, readonly_indexes: table.readonly_indexes, }); @@ -427,18 +426,18 @@ pub mod convert_from { pub fn create_message_instructions( ixs: Vec, - ) -> Result, String> { + ) -> CreateResult> { ixs.into_iter().map(create_message_instruction).collect() } pub fn create_message_instruction( ix: proto::CompiledInstruction, - ) -> Result { + ) -> CreateResult { Ok(CompiledInstruction { - program_id_index: ensure_some( - ix.program_id_index.try_into().ok(), - "failed to decode CompiledInstruction.program_id_index)", - )?, + program_id_index: ix + .program_id_index + .try_into() + .map_err(|_| "failed to decode CompiledInstruction.program_id_index)")?, accounts: ix.accounts, data: ix.data, }) @@ -446,7 +445,7 @@ pub mod convert_from { pub fn create_tx_meta( meta: proto::TransactionStatusMeta, - ) -> Result { + ) -> CreateResult { let meta_status = match create_tx_error(meta.err.as_ref())? { Some(err) => Err(err), None => Ok(()), @@ -474,12 +473,10 @@ pub mod convert_from { return_data: if meta.return_data_none { None } else { - let data = ensure_some(meta.return_data, "failed to get return_data")?; + let data = meta.return_data.ok_or("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", - )?, + program_id: Pubkey::try_from(data.program_id.as_slice()) + .map_err(|_| "failed to parse program_id")?, data: data.data, }) }, @@ -489,32 +486,29 @@ pub mod convert_from { 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", - ) + ) -> CreateResult> { + err.map(|err| bincode::deserialize::(&err.err)) + .transpose() + .map_err(|_| "failed to decode TransactionError") } pub fn create_meta_inner_instructions( ixs: Vec, - ) -> Result, String> { + ) -> CreateResult> { ixs.into_iter().map(create_meta_inner_instruction).collect() } pub fn create_meta_inner_instruction( ix: proto::InnerInstructions, - ) -> Result { + ) -> CreateResult { 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)", - )?, + program_id_index: ix + .program_id_index + .try_into() + .map_err(|_| "failed to decode CompiledInstruction.program_id_index)")?, accounts: ix.accounts, data: ix.data, }, @@ -522,15 +516,15 @@ pub mod convert_from { }); } Ok(InnerInstructions { - index: ensure_some( - ix.index.try_into().ok(), - "failed to decode InnerInstructions.index", - )?, + index: ix + .index + .try_into() + .map_err(|_| "failed to decode InnerInstructions.index")?, instructions, }) } - pub fn create_rewards_obj(rewards: proto::Rewards) -> Result { + pub fn create_rewards_obj(rewards: proto::Rewards) -> CreateResult { Ok(RewardsAndNumPartitions { rewards: rewards .rewards @@ -541,15 +535,14 @@ pub mod convert_from { }) } - pub fn create_reward(reward: proto::Reward) -> Result { + pub fn create_reward(reward: proto::Reward) -> CreateResult { Ok(Reward { pubkey: reward.pubkey, lamports: reward.lamports, post_balance: reward.post_balance, - reward_type: match ensure_some( - proto::RewardType::try_from(reward.reward_type).ok(), - "failed to parse reward_type", - )? { + reward_type: match proto::RewardType::try_from(reward.reward_type) + .map_err(|_| "failed to parse reward_type")? + { proto::RewardType::Unspecified => None, proto::RewardType::Fee => Some(RewardType::Fee), proto::RewardType::Rent => Some(RewardType::Rent), @@ -559,32 +552,36 @@ pub mod convert_from { commission: if reward.commission.is_empty() { None } else { - Some(ensure_some( - reward.commission.parse().ok(), - "failed to parse reward commission", - )?) + Some( + reward + .commission + .parse() + .map_err(|_| "failed to parse reward commission")?, + ) }, }) } pub fn create_token_balances( balances: Vec, - ) -> Result, String> { + ) -> CreateResult> { 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")?; + let ui_amount = balance + .ui_token_amount + .ok_or("failed to get ui_token_amount")?; vec.push(TransactionTokenBalance { - account_index: ensure_some( - balance.account_index.try_into().ok(), - "failed to parse account_index", - )?, + account_index: balance + .account_index + .try_into() + .map_err(|_| "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", - )?, + decimals: ui_amount + .decimals + .try_into() + .map_err(|_| "failed to parse decimals")?, amount: ui_amount.amount, ui_amount_string: ui_amount.ui_amount_string, }, @@ -598,27 +595,27 @@ pub mod convert_from { pub fn create_loaded_addresses( writable: Vec>, readonly: Vec>, - ) -> Result { + ) -> CreateResult { Ok(LoadedAddresses { writable: create_pubkey_vec(writable)?, readonly: create_pubkey_vec(readonly)?, }) } - pub fn create_pubkey_vec(pubkeys: Vec>) -> Result, String> { + pub fn create_pubkey_vec(pubkeys: Vec>) -> CreateResult> { pubkeys .iter() .map(|pubkey| create_pubkey(pubkey.as_slice())) .collect() } - pub fn create_pubkey(pubkey: &[u8]) -> Result { - ensure_some(Pubkey::try_from(pubkey).ok(), "failed to parse Pubkey") + pub fn create_pubkey(pubkey: &[u8]) -> CreateResult { + Pubkey::try_from(pubkey).map_err(|_| "failed to parse Pubkey") } pub fn create_account( account: proto::SubscribeUpdateAccountInfo, - ) -> Result<(Pubkey, Account), String> { + ) -> CreateResult<(Pubkey, Account)> { let pubkey = create_pubkey(&account.pubkey)?; let account = Account { lamports: account.lamports, From 68a965cf84a192988704a2b4bfcdcd281729186b Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 15 Nov 2024 09:04:28 +0200 Subject: [PATCH 48/50] add verify flag to client --- examples/rust/src/bin/client.rs | 97 +++++-- .../src/plugin/message_ref.rs | 241 +++++++++++++++++- 2 files changed, 314 insertions(+), 24 deletions(-) diff --git a/examples/rust/src/bin/client.rs b/examples/rust/src/bin/client.rs index 0ba205b9..418bcfe6 100644 --- a/examples/rust/src/bin/client.rs +++ b/examples/rust/src/bin/client.rs @@ -8,12 +8,19 @@ use { serde_json::{json, Value}, solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature}, solana_transaction_status::UiTransactionEncoding, - std::{collections::HashMap, env, fs::File, sync::Arc, time::Duration}, - tokio::sync::Mutex, + std::{ + collections::HashMap, + env, + fs::File, + sync::Arc, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, + }, + tokio::{fs, sync::Mutex}, tonic::transport::channel::ClientTlsConfig, yellowstone_grpc_client::{GeyserGrpcClient, GeyserGrpcClientError, Interceptor}, yellowstone_grpc_proto::{ convert_from, + plugin::{filter::FilterName, message_ref::Message as MessageRef}, prelude::{ subscribe_request_filter_accounts_filter::Filter as AccountsFilterOneof, subscribe_request_filter_accounts_filter_lamports::Cmp as AccountsFilterLamports, @@ -258,13 +265,16 @@ struct ActionSubscribe { /// Show total stat instead of messages #[clap(long, default_value_t = false)] stats: bool, + + #[clap(long, default_value_t = false)] + verify_encoding: bool, } impl Action { async fn get_subscribe_request( &self, commitment: Option, - ) -> anyhow::Result> { + ) -> anyhow::Result> { Ok(match self { Self::Subscribe(args) => { let mut accounts: AccountFilterMap = HashMap::new(); @@ -439,6 +449,7 @@ impl Action { }, args.resub.unwrap_or(0), args.stats, + args.verify_encoding, )) } _ => None, @@ -485,7 +496,7 @@ async fn main() -> anyhow::Result<()> { .map(|response| info!("response: {response:?}")), Action::HealthWatch => geyser_health_watch(client).await, Action::Subscribe(_) => { - let (request, resub, stats) = args + let (request, resub, stats, verify_encoding) = args .action .get_subscribe_request(commitment) .await @@ -494,7 +505,7 @@ async fn main() -> anyhow::Result<()> { "expect subscribe action" )))?; - geyser_subscribe(client, request, resub, stats).await + geyser_subscribe(client, request, resub, stats, verify_encoding).await } Action::Ping { count } => client .ping(*count) @@ -552,26 +563,29 @@ async fn geyser_subscribe( request: SubscribeRequest, resub: usize, stats: bool, + verify_encoding: bool, ) -> anyhow::Result<()> { let pb_multi = MultiProgress::new(); let mut pb_accounts_c = 0; - let pb_accounts = crate_progress_bar(&pb_multi, "accounts", false)?; + let pb_accounts = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("accounts"))?; let mut pb_slots_c = 0; - let pb_slots = crate_progress_bar(&pb_multi, "slots", false)?; + let pb_slots = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("slots"))?; let mut pb_txs_c = 0; - let pb_txs = crate_progress_bar(&pb_multi, "transactions", false)?; + let pb_txs = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("transactions"))?; let mut pb_txs_st_c = 0; - let pb_txs_st = crate_progress_bar(&pb_multi, "transactions statuses", false)?; + let pb_txs_st = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("transactions statuses"))?; let mut pb_entries_c = 0; - let pb_entries = crate_progress_bar(&pb_multi, "entries", false)?; + let pb_entries = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("entries"))?; let mut pb_blocks_mt_c = 0; - let pb_blocks_mt = crate_progress_bar(&pb_multi, "blocks meta", false)?; + let pb_blocks_mt = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("blocks meta"))?; let mut pb_blocks_c = 0; - let pb_blocks = crate_progress_bar(&pb_multi, "blocks", false)?; + let pb_blocks = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("blocks"))?; let mut pb_pp_c = 0; - let pb_pp = crate_progress_bar(&pb_multi, "ping/pong", false)?; + let pb_pp = crate_progress_bar(&pb_multi, ProgressBarTpl::Msg("ping/pong"))?; let mut pb_total_c = 0; - let pb_total = crate_progress_bar(&pb_multi, "total", true)?; + let pb_total = crate_progress_bar(&pb_multi, ProgressBarTpl::Total)?; + let mut pb_verify_c = verify_encoding.then_some((0, 0)); + let pb_verify = crate_progress_bar(&pb_multi, ProgressBarTpl::Verify)?; let (mut subscribe_tx, mut stream) = client.subscribe_with_request(Some(request)).await?; @@ -603,6 +617,38 @@ async fn geyser_subscribe( pb_total_c += 1; pb_total.set_message(format_thousands(pb_total_c)); pb_total.inc(encoded_len); + if let Some((prost_c, ref_c)) = &mut pb_verify_c { + // let msg = msg.update_oneof.ok_or(anyhow::anyhow!("no update"))?; + let ts = Instant::now(); + let encoded_len_prost = msg.encoded_len(); + let encoded_prost = msg.clone().encode_to_vec(); + *prost_c += ts.elapsed().as_nanos(); + // Temporary `convert`, need to implement in proto crate + let message = MessageRef::new( + msg.filters.into_iter().map(FilterName::new).collect(), + msg.update_oneof.expect("no update message").into(), + ); + let ts = Instant::now(); + let encoded_len_ref = message.encoded_len(); + let encoded_ref = message.encode_to_vec(); + *ref_c += ts.elapsed().as_nanos(); + pb_verify.set_message(format!( + "{:.2?}%", + 100f64 * (*ref_c as f64) / (*prost_c as f64) + )); + if encoded_len_prost != encoded_len_ref || encoded_prost != encoded_ref { + let dir = "grpc-client-verify"; + let name = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos(); + let path = format!("{dir}/{name}"); + error!("found unmached message, save to `{path}`"); + fs::create_dir(dir) + .await + .context("failed to create dir for unmached")?; + fs::write(path, encoded_prost) + .await + .context("failed to save unmached")?; + } + } continue; } @@ -752,14 +798,29 @@ async fn geyser_subscribe( Ok(()) } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ProgressBarTpl { + Msg(&'static str), + Total, + Verify, +} + fn crate_progress_bar( pb: &MultiProgress, - kind: &str, - elapsed: bool, + pb_t: ProgressBarTpl, ) -> Result { let pb = pb.add(ProgressBar::no_length()); - let elapsed = if elapsed { " in {elapsed_precise}" } else { "" }; - let tpl = format!("{{spinner}} {kind}: {{msg}} / ~{{bytes}} (~{{bytes_per_sec}}){elapsed}"); + let tpl = match pb_t { + ProgressBarTpl::Msg(kind) => { + format!("{{spinner}} {kind}: {{msg}} / ~{{bytes}} (~{{bytes_per_sec}})") + } + ProgressBarTpl::Total => { + "{spinner} total: {msg} / ~{bytes} (~{bytes_per_sec}) in {elapsed_precise}".to_owned() + } + ProgressBarTpl::Verify => { + "{spinner} verify: {msg} (elapsed time, compare to prost)".to_owned() + } + }; pb.set_style(ProgressStyle::with_template(&tpl)?); Ok(pb) } diff --git a/yellowstone-grpc-proto/src/plugin/message_ref.rs b/yellowstone-grpc-proto/src/plugin/message_ref.rs index 520126dd..acca99fa 100644 --- a/yellowstone-grpc-proto/src/plugin/message_ref.rs +++ b/yellowstone-grpc-proto/src/plugin/message_ref.rs @@ -7,7 +7,7 @@ use { }, }, crate::{ - convert_to, + convert_from, convert_to, geyser::{ subscribe_update::UpdateOneof, CommitmentLevel as CommitmentLevelProto, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, @@ -29,18 +29,21 @@ use { smallvec::SmallVec, solana_account_decoder::parse_token::UiTokenAmount, solana_sdk::{ + hash::{Hash, HASH_BYTES}, instruction::CompiledInstruction, message::{ - v0::{LoadedMessage, MessageAddressTableLookup}, - LegacyMessage, MessageHeader, SanitizedMessage, + v0::{LoadedAddresses, LoadedMessage, MessageAddressTableLookup}, + AddressLoader, AddressLoaderError, LegacyMessage, MessageHeader, SanitizedMessage, }, - transaction::SanitizedTransaction, + pubkey::Pubkey, + signature::{Keypair, Signature, Signer}, + transaction::{MessageHash, SanitizedTransaction, Transaction}, transaction_context::TransactionReturnData, }, solana_transaction_status::{ InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta, TransactionTokenBalance, }, - std::{borrow::Cow, sync::Arc}, + std::{borrow::Cow, collections::HashSet, sync::Arc}, }; #[inline] @@ -157,6 +160,22 @@ impl From<&MessageRef> for UpdateOneof { } } +impl From for MessageRef { + fn from(message: UpdateOneof) -> Self { + match message { + UpdateOneof::Account(msg) => Self::Account(msg.into()), + UpdateOneof::Slot(msg) => Self::Slot(msg.into()), + UpdateOneof::Transaction(msg) => Self::Transaction(msg.into()), + UpdateOneof::TransactionStatus(msg) => Self::TransactionStatus(msg.into()), + UpdateOneof::Entry(msg) => Self::Entry(Arc::new(msg.into())), + UpdateOneof::BlockMeta(msg) => Self::BlockMeta(Arc::new(msg.into())), + UpdateOneof::Block(msg) => Self::Block(Box::new(msg.into())), + UpdateOneof::Ping(SubscribeUpdatePing {}) => Self::Ping, + UpdateOneof::Pong(SubscribeUpdatePong { id }) => Self::pong(id), + } + } +} + impl prost::Message for MessageRef { fn encode_raw(&self, buf: &mut impl BufMut) { match self { @@ -259,7 +278,7 @@ pub struct MessageAccountRef { impl From<&MessageAccountRef> for SubscribeUpdateAccount { fn from(msg: &MessageAccountRef) -> Self { - SubscribeUpdateAccount { + Self { account: Some((msg.account.as_ref(), &msg.data_slice).into()), slot: msg.slot, is_startup: msg.is_startup, @@ -267,6 +286,17 @@ impl From<&MessageAccountRef> for SubscribeUpdateAccount { } } +impl From for MessageAccountRef { + fn from(msg: SubscribeUpdateAccount) -> Self { + Self { + account: Arc::new(msg.account.expect("no account message").into()), + slot: msg.slot, + is_startup: msg.is_startup, + data_slice: FilterAccountsDataSlice::default(), + } + } +} + impl From<(&MessageAccountInfo, &FilterAccountsDataSlice)> for SubscribeUpdateAccountInfo { fn from((account, data_slice): (&MessageAccountInfo, &FilterAccountsDataSlice)) -> Self { SubscribeUpdateAccountInfo { @@ -282,6 +312,23 @@ impl From<(&MessageAccountInfo, &FilterAccountsDataSlice)> for SubscribeUpdateAc } } +impl From for MessageAccountInfo { + fn from(account: SubscribeUpdateAccountInfo) -> Self { + Self { + pubkey: Pubkey::try_from(account.pubkey).expect("invalid pubkey"), + lamports: account.lamports, + owner: Pubkey::try_from(account.owner).expect("invalid owner"), + executable: account.executable, + rent_epoch: account.rent_epoch, + data: account.data, + write_version: account.write_version, + txn_signature: account + .txn_signature + .map(|sig| Signature::try_from(sig).expect("invalid signature")), + } + } +} + impl prost::Message for MessageAccountRef { fn encode_raw(&self, buf: &mut impl BufMut) { Self::account_encode_raw(1u32, &self.account, &self.data_slice, buf); @@ -441,6 +488,18 @@ impl From<&MessageSlot> for SubscribeUpdateSlot { } } +impl From for MessageSlot { + fn from(msg: SubscribeUpdateSlot) -> Self { + Self { + slot: msg.slot, + parent: msg.parent, + status: CommitmentLevelProto::try_from(msg.status) + .expect("valid commitment") + .into(), + } + } +} + impl prost::Message for MessageSlot { fn encode_raw(&self, buf: &mut impl BufMut) { let status = CommitmentLevelProto::from(self.status) as i32; @@ -501,6 +560,15 @@ impl From<&MessageTransactionRef> for SubscribeUpdateTransaction { } } +impl From for MessageTransactionRef { + fn from(msg: SubscribeUpdateTransaction) -> Self { + Self { + transaction: Arc::new(msg.transaction.expect("no transaction message").into()), + slot: msg.slot, + } + } +} + impl From<&MessageTransactionInfo> for SubscribeUpdateTransactionInfo { fn from(tx: &MessageTransactionInfo) -> Self { SubscribeUpdateTransactionInfo { @@ -513,6 +581,43 @@ impl From<&MessageTransactionInfo> for SubscribeUpdateTransactionInfo { } } +impl From for MessageTransactionInfo { + fn from(msg: SubscribeUpdateTransactionInfo) -> Self { + #[derive(Debug, Clone)] + struct SimpleAddressLoader; + + impl AddressLoader for SimpleAddressLoader { + fn load_addresses( + self, + _lookups: &[MessageAddressTableLookup], + ) -> Result { + Ok(LoadedAddresses::default()) + } + } + + let versioned_tx = + convert_from::create_tx_versioned(msg.transaction.expect("no transaction message")) + .expect("invalid transaction message"); + let transaction = SanitizedTransaction::try_create( + versioned_tx, + MessageHash::Compute, + None, + SimpleAddressLoader, + &HashSet::new(), + ) + .expect("failed to create tx"); + + Self { + signature: Signature::try_from(msg.signature).expect("invalid signature"), + is_vote: msg.is_vote, + transaction, + meta: convert_from::create_tx_meta(msg.meta.expect("no meta message")) + .expect("invalid meta message"), + index: msg.index as usize, + } + } +} + impl prost::Message for MessageTransactionRef { fn encode_raw(&self, buf: &mut impl BufMut) { Self::tx_meta_encode_raw(1u32, &self.transaction, buf); @@ -1112,6 +1217,45 @@ impl From<&MessageTransactionStatusRef> for SubscribeUpdateTransactionStatus { } } +impl From for MessageTransactionStatusRef { + fn from(msg: SubscribeUpdateTransactionStatus) -> Self { + let keypair = Keypair::new(); + let message = solana_sdk::message::Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![keypair.pubkey()], + ..solana_sdk::message::Message::default() + }; + + let transaction = SanitizedTransaction::from_transaction_for_tests(Transaction::new( + &[&keypair], + message, + Hash::default(), + )); + + Self { + slot: msg.slot, + transaction: Arc::new(MessageTransactionInfo { + signature: Signature::try_from(msg.signature).expect("invalid signature"), + is_vote: msg.is_vote, + transaction, + meta: TransactionStatusMeta { + status: match convert_from::create_tx_error(msg.err.as_ref()) + .expect("invalid meta err") + { + Some(err) => Err(err), + None => Ok(()), + }, + ..Default::default() + }, + index: msg.index as usize, + }), + } + } +} + impl prost::Message for MessageTransactionStatusRef { fn encode_raw(&self, buf: &mut impl BufMut) { if self.slot != 0u64 { @@ -1215,6 +1359,54 @@ impl From<&MessageRefBlock> for SubscribeUpdateBlock { } } +impl From for MessageRefBlock { + fn from(msg: SubscribeUpdateBlock) -> Self { + let mut rewards = msg + .rewards + .map(|rewards| convert_from::create_rewards_obj(rewards).expect("invalid rewards")); + + let meta = Arc::new(MessageBlockMeta { + parent_slot: msg.parent_slot, + slot: msg.slot, + parent_blockhash: msg.parent_blockhash, + blockhash: msg.blockhash, + rewards: rewards + .as_mut() + .map(|rewards| std::mem::take(&mut rewards.rewards)) + .unwrap_or_default(), + num_partitions: rewards.and_then(|obj| obj.num_partitions), + block_time: msg.block_time.map(|wrapper| wrapper.timestamp), + block_height: msg.block_height.map(|wrapper| wrapper.block_height), + executed_transaction_count: msg.executed_transaction_count, + entries_count: msg.entries_count, + }); + + Self { + meta, + transactions: msg + .transactions + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect(), + updated_account_count: msg.updated_account_count, + accounts: msg + .accounts + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect(), + accounts_data_slice: FilterAccountsDataSlice::default(), + entries: msg + .entries + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect(), + } + } +} + impl prost::Message for MessageRefBlock { fn encode_raw(&self, buf: &mut impl BufMut) { if self.meta.slot != 0u64 { @@ -1359,6 +1551,30 @@ impl From<&MessageBlockMeta> for SubscribeUpdateBlockMeta { } } +impl From for MessageBlockMeta { + fn from(msg: SubscribeUpdateBlockMeta) -> Self { + let mut rewards = msg + .rewards + .map(|rewards| convert_from::create_rewards_obj(rewards).expect("invalid rewards")); + + Self { + parent_slot: msg.parent_slot, + slot: msg.slot, + parent_blockhash: msg.parent_blockhash, + blockhash: msg.blockhash, + rewards: rewards + .as_mut() + .map(|rewards| std::mem::take(&mut rewards.rewards)) + .unwrap_or_default(), + num_partitions: rewards.and_then(|obj| obj.num_partitions), + block_time: msg.block_time.map(|wrapper| wrapper.timestamp), + block_height: msg.block_height.map(|wrapper| wrapper.block_height), + executed_transaction_count: msg.executed_transaction_count, + entries_count: msg.entries_count, + } + } +} + impl prost::Message for MessageBlockMeta { fn encode_raw(&self, buf: &mut impl BufMut) { if self.slot != 0u64 { @@ -1564,6 +1780,19 @@ impl From<&MessageEntry> for SubscribeUpdateEntry { } } +impl From for MessageEntry { + fn from(msg: SubscribeUpdateEntry) -> Self { + Self { + slot: msg.slot, + index: msg.index as usize, + num_hashes: msg.num_hashes, + hash: <[u8; HASH_BYTES]>::try_from(msg.hash).expect("invalid entry hash"), + executed_transaction_count: msg.executed_transaction_count, + starting_transaction_index: msg.starting_transaction_index, + } + } +} + impl prost::Message for MessageEntry { fn encode_raw(&self, buf: &mut impl BufMut) { let index = self.index as u64; From 39fb4b7037e82775eff05b9b17297493dd62a149 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 15 Nov 2024 09:14:41 +0200 Subject: [PATCH 49/50] add fs feat to client --- examples/rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index ee0ca7b9..b663d2c8 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -28,7 +28,7 @@ maplit = { workspace = true } serde_json = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread"] } +tokio = { workspace = true, features = ["rt-multi-thread", "fs"] } tonic = { workspace = true } yellowstone-grpc-client = { workspace = true } yellowstone-grpc-proto = { workspace = true, default-features = true } From ec3d8ea22321d330c948d6b5bd501885066ce877 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Fri, 15 Nov 2024 09:42:52 +0200 Subject: [PATCH 50/50] add plugin feature to client --- examples/rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index b663d2c8..cb300d0d 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -31,7 +31,7 @@ solana-transaction-status = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "fs"] } tonic = { workspace = true } yellowstone-grpc-client = { workspace = true } -yellowstone-grpc-proto = { workspace = true, default-features = true } +yellowstone-grpc-proto = { workspace = true, default-features = true, features = ["plugin"] } [lints] workspace = true