// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs // version: 8 const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128; const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536; const MAX_GRPC_SUBSCRIBE_ACCOUNT_DATA_LENGTH_BYTES: usize = 512 * 1024 * 1024; const MAX_GRPC_SUBSCRIBE_ACCOUNT_PREDICATE_COUNT: usize = 256; const MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT: usize = 50_000; const MAX_GRPC_SUBSCRIBE_CUCKOO_DATA_LENGTH_BYTES: usize = 512 * 1024 * 1024; const MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT: usize = 128; const MAX_GRPC_SUBSCRIBE_DATA_SLICE_LENGTH_BYTES: u64 = 64 * 1024 * 1024; const MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT: usize = 1_024; const MAX_GRPC_SUBSCRIBE_FILTER_NAME_LENGTH_BYTES: usize = 128; const MAX_GRPC_SUBSCRIBE_MEMCMP_BYTES: usize = 1024 * 1024; const MAX_GRPC_SUBSCRIBE_MEMCMP_TEXT_LENGTH_BYTES: usize = 2 * 1024 * 1024; const MAX_GRPC_SUBSCRIBE_SLOT_DEAD_ERROR_LENGTH_BYTES: usize = 16 * 1024; const MAX_GRPC_SUBSCRIBE_TRANSACTION_SIGNATURE_TEXT_LENGTH_BYTES: usize = 128; const MAX_GRPC_SUBSCRIBE_UPDATE_FILTER_COUNT: usize = 1_024; const MAX_GRPC_TRANSACTION_ERROR_BYTES: usize = 64 * 1024; const MAX_GRPC_TRANSACTION_INSTRUCTION_DATA_BYTES: usize = 1024 * 1024; const MAX_GRPC_TRANSACTION_LOG_COUNT: usize = 16_384; const MAX_GRPC_TRANSACTION_LOG_LENGTH_BYTES: usize = 64 * 1024; const MAX_GRPC_TRANSACTION_RETURN_DATA_BYTES: usize = 1024 * 1024; const MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES: usize = 64 * 1024; const MAX_GRPC_TRANSACTION_VECTOR_COUNT: usize = 65_536; const YELLOWSTONE_HASH_LENGTH_BYTES: usize = 32; const YELLOWSTONE_TRANSACTION_SIGNATURE_WIRE_LENGTH_BYTES: usize = 64; /// Validated logical filter name used by the standard Yellowstone `SubscribeRequest` maps. /// /// Filter names are returned by Yellowstone in update envelopes. KSP therefore keeps them as typed public data, but omits their text from `Debug` so routine /// diagnostics cannot accidentally disclose caller-defined labels. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct YellowstoneSubscribeFilterName { value: std::string::String, } impl YellowstoneSubscribeFilterName { /// Validates one provider-neutral Yellowstone filter-group name. pub fn new(value: impl std::convert::Into) -> ksp_core_lib::Result { let value = value.into(); if value.is_empty() || value.len() > MAX_GRPC_SUBSCRIBE_FILTER_NAME_LENGTH_BYTES || value.trim() != value || value.chars().any(char::is_control) { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter name is invalid") .with_context("field", "grpc_subscribe.filter_name"), ); } return std::result::Result::Ok(Self { value }); } /// Returns the validated filter name text. #[must_use] pub fn as_str(&self) -> &str { return self.value.as_str(); } } impl std::fmt::Debug for YellowstoneSubscribeFilterName { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("YellowstoneSubscribeFilterName()"); } } /// One standard Yellowstone account-data slice applied to subscribed account payloads. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct YellowstoneAccountsDataSlice { offset: u64, length: u64, } impl YellowstoneAccountsDataSlice { /// Creates and validates one account-data slice. pub fn new(offset: u64, length: u64) -> ksp_core_lib::Result { if length > MAX_GRPC_SUBSCRIBE_DATA_SLICE_LENGTH_BYTES || offset.checked_add(length).is_none() { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice exceeds KSP bounds") .with_context("field", "grpc_subscribe.accounts_data_slice"), ); } return std::result::Result::Ok(Self { offset, length }); } /// Returns the byte offset. #[must_use] pub const fn offset(self) -> u64 { return self.offset; } /// Returns the requested byte length. #[must_use] pub const fn length(self) -> u64 { return self.length; } fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestAccountsDataSlice { return yellowstone_grpc_proto::geyser::SubscribeRequestAccountsDataSlice { offset: self.offset, length: self.length }; } } /// Optional ping mutation carried inside the standard Yellowstone subscribe stream. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct YellowstoneSubscribePing { id: i32, } impl YellowstoneSubscribePing { /// Creates one subscribe-stream ping request. #[must_use] pub const fn new(id: i32) -> Self { return Self { id }; } /// Returns the exact ping identifier carried on the wire. #[must_use] pub const fn id(self) -> i32 { return self.id; } fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestPing { return yellowstone_grpc_proto::geyser::SubscribeRequestPing { id: self.id }; } } /// Hash algorithm carried by a standard Yellowstone compressed Cuckoo account filter. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneCuckooHashAlgorithm { /// Stable SipHash algorithm currently published by Yellowstone. SipHash, } impl YellowstoneCuckooHashAlgorithm { const fn to_wire(self) -> i32 { return match self { Self::SipHash => yellowstone_grpc_proto::geyser::CuckooHashAlgorithm::SipHash as i32, }; } } /// Wire-preserving compressed Cuckoo filter used by current Yellowstone account subscriptions. /// /// KSP owns the public representation and bounds the serialized bucket data. The current upstream wire documents fingerprint sizes of 8, 12 or 16 bits and /// publishes SipHash as its only hash algorithm. `Debug` never renders the bucket bytes. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneCuckooFilter { data: std::vec::Vec, bucket_count: u32, entries_per_bucket: u32, fingerprint_bits: u32, hash_seed: u64, hash_algorithm: crate::YellowstoneCuckooHashAlgorithm, } impl YellowstoneCuckooFilter { /// Creates one bounded standard Yellowstone Cuckoo filter. pub fn new( data: std::vec::Vec, bucket_count: u32, entries_per_bucket: u32, fingerprint_bits: u32, hash_seed: u64, hash_algorithm: crate::YellowstoneCuckooHashAlgorithm, ) -> ksp_core_lib::Result { if data.len() > MAX_GRPC_SUBSCRIBE_CUCKOO_DATA_LENGTH_BYTES || bucket_count == 0 || entries_per_bucket == 0 || !matches!(fingerprint_bits, 8 | 12 | 16) { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone Cuckoo filter violates KSP structural bounds") .with_context("field", "grpc_subscribe.cuckoo_filter"), ); } return std::result::Result::Ok(Self { data, bucket_count, entries_per_bucket, fingerprint_bits, hash_seed, hash_algorithm }); } /// Returns the serialized bucket data without copying it. #[must_use] pub fn data(&self) -> &[u8] { return self.data.as_slice(); } /// Returns the number of Cuckoo buckets. #[must_use] pub const fn bucket_count(&self) -> u32 { return self.bucket_count; } /// Returns the number of entries carried by one bucket. #[must_use] pub const fn entries_per_bucket(&self) -> u32 { return self.entries_per_bucket; } /// Returns the fingerprint width in bits. #[must_use] pub const fn fingerprint_bits(&self) -> u32 { return self.fingerprint_bits; } /// Returns the deterministic hash seed carried on the wire. #[must_use] pub const fn hash_seed(&self) -> u64 { return self.hash_seed; } /// Returns the hash algorithm carried on the wire. #[must_use] pub const fn hash_algorithm(&self) -> crate::YellowstoneCuckooHashAlgorithm { return self.hash_algorithm; } fn to_wire(&self) -> yellowstone_grpc_proto::geyser::CuckooFilter { return yellowstone_grpc_proto::geyser::CuckooFilter { data: self.data.clone(), bucket_count: self.bucket_count, entries_per_bucket: self.entries_per_bucket, fingerprint_bits: self.fingerprint_bits, hash_seed: self.hash_seed, hash_algorithm: self.hash_algorithm.to_wire(), }; } } impl std::fmt::Debug for YellowstoneCuckooFilter { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneCuckooFilter") .field("data_length", &self.data.len()) .field("bucket_count", &self.bucket_count) .field("entries_per_bucket", &self.entries_per_bucket) .field("fingerprint_bits", &self.fingerprint_bits) .field("hash_seed", &self.hash_seed) .field("hash_algorithm", &self.hash_algorithm) .finish(); } } #[derive(Clone, Eq, PartialEq)] enum YellowstoneAccountMemcmpData { Bytes(std::vec::Vec), Base58(std::string::String), Base64(std::string::String), } /// Encoding selected by one standard Yellowstone account `memcmp` predicate. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneAccountMemcmpEncoding { /// Raw bytes. Bytes, /// Base58 text. Base58, /// Base64 text. Base64, } /// Validated standard Yellowstone `memcmp` account predicate. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneAccountMemcmp { offset: u64, data: YellowstoneAccountMemcmpData, } impl YellowstoneAccountMemcmp { /// Creates a raw-byte `memcmp` predicate. pub fn bytes(offset: u64, data: std::vec::Vec) -> ksp_core_lib::Result { if data.len() > MAX_GRPC_SUBSCRIBE_MEMCMP_BYTES { return invalid_subscribe_parameter("grpc_subscribe.accounts.filters.memcmp.bytes", "Yellowstone memcmp byte payload exceeds the KSP bound"); } return std::result::Result::Ok(Self { offset, data: YellowstoneAccountMemcmpData::Bytes(data) }); } /// Creates a bounded base58-text `memcmp` predicate. pub fn base58(offset: u64, data: impl std::convert::Into) -> ksp_core_lib::Result { let data = data.into(); if let std::result::Result::Err(error) = validate_memcmp_text(&data) { return std::result::Result::Err(error); } return std::result::Result::Ok(Self { offset, data: YellowstoneAccountMemcmpData::Base58(data) }); } /// Creates a bounded base64-text `memcmp` predicate. pub fn base64(offset: u64, data: impl std::convert::Into) -> ksp_core_lib::Result { let data = data.into(); if let std::result::Result::Err(error) = validate_memcmp_text(&data) { return std::result::Result::Err(error); } return std::result::Result::Ok(Self { offset, data: YellowstoneAccountMemcmpData::Base64(data) }); } /// Returns the byte offset. #[must_use] pub const fn offset(&self) -> u64 { return self.offset; } /// Returns the selected wire encoding. #[must_use] pub fn encoding(&self) -> crate::YellowstoneAccountMemcmpEncoding { return match &self.data { YellowstoneAccountMemcmpData::Bytes(_) => crate::YellowstoneAccountMemcmpEncoding::Bytes, YellowstoneAccountMemcmpData::Base58(_) => crate::YellowstoneAccountMemcmpEncoding::Base58, YellowstoneAccountMemcmpData::Base64(_) => crate::YellowstoneAccountMemcmpEncoding::Base64, }; } /// Returns the raw-byte payload when this predicate uses bytes. #[must_use] pub fn bytes_value(&self) -> std::option::Option<&[u8]> { return match &self.data { YellowstoneAccountMemcmpData::Bytes(value) => std::option::Option::Some(value.as_slice()), YellowstoneAccountMemcmpData::Base58(_) | YellowstoneAccountMemcmpData::Base64(_) => std::option::Option::None, }; } /// Returns the base58/base64 text when this predicate uses a textual encoding. #[must_use] pub fn text_value(&self) -> std::option::Option<&str> { return match &self.data { YellowstoneAccountMemcmpData::Base58(value) | YellowstoneAccountMemcmpData::Base64(value) => std::option::Option::Some(value.as_str()), YellowstoneAccountMemcmpData::Bytes(_) => std::option::Option::None, }; } fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccountsFilterMemcmp { let data = match &self.data { YellowstoneAccountMemcmpData::Bytes(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_memcmp::Data::Bytes(value.clone()) }, YellowstoneAccountMemcmpData::Base58(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_memcmp::Data::Base58(value.clone()) }, YellowstoneAccountMemcmpData::Base64(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_memcmp::Data::Base64(value.clone()) }, }; return yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccountsFilterMemcmp { offset: self.offset, data: std::option::Option::Some(data) }; } } impl std::fmt::Debug for YellowstoneAccountMemcmp { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let payload_length = match &self.data { YellowstoneAccountMemcmpData::Bytes(value) => value.len(), YellowstoneAccountMemcmpData::Base58(value) | YellowstoneAccountMemcmpData::Base64(value) => value.len(), }; return formatter .debug_struct("YellowstoneAccountMemcmp") .field("offset", &self.offset) .field("encoding", &self.encoding()) .field("payload_length", &payload_length) .finish(); } } /// Lamport comparison used by a standard Yellowstone account filter. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneAccountLamportsFilter { /// Lamports equal to the value. Eq(u64), /// Lamports not equal to the value. Ne(u64), /// Lamports lower than the value. Lt(u64), /// Lamports greater than the value. Gt(u64), } /// One standard Yellowstone account predicate. #[derive(Clone, Debug, Eq, PartialEq)] pub enum YellowstoneAccountFilterPredicate { /// Compare account data at one offset. Memcmp(crate::YellowstoneAccountMemcmp), /// Require an exact account-data size. DataSize(u64), /// Require the SPL Token account-state predicate to evaluate to the supplied boolean. TokenAccountState(bool), /// Compare the account lamport balance. Lamports(crate::YellowstoneAccountLamportsFilter), } impl YellowstoneAccountFilterPredicate { fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccountsFilter { let filter = match self { Self::Memcmp(value) => yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Memcmp(value.to_wire()), Self::DataSize(value) => yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Datasize(*value), Self::TokenAccountState(value) => yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::TokenAccountState(*value), Self::Lamports(value) => { let cmp = match value { crate::YellowstoneAccountLamportsFilter::Eq(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Eq(*value) }, crate::YellowstoneAccountLamportsFilter::Ne(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Ne(*value) }, crate::YellowstoneAccountLamportsFilter::Lt(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Lt(*value) }, crate::YellowstoneAccountLamportsFilter::Gt(value) => { yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Gt(*value) }, }; yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Lamports( yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccountsFilterLamports { cmp: std::option::Option::Some(cmp) }, ) }, }; return yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccountsFilter { filter: std::option::Option::Some(filter) }; } } /// Complete account-family filter group for the standard Yellowstone subscribe surface. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribeAccountFilter { accounts: std::vec::Vec, owners: std::vec::Vec, filters: std::vec::Vec, nonempty_txn_signature: std::option::Option, cuckoo_accounts_filter: std::option::Option, } impl YellowstoneSubscribeAccountFilter { /// Creates an empty account filter group. #[must_use] pub fn new() -> Self { return Self { accounts: std::vec::Vec::new(), owners: std::vec::Vec::new(), filters: std::vec::Vec::new(), nonempty_txn_signature: std::option::Option::None, cuckoo_accounts_filter: std::option::Option::None, }; } /// Adds one exact account selector. pub fn push_account(&mut self, account: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { if self.accounts.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT { return invalid_subscribe_parameter("grpc_subscribe.accounts.account", "Yellowstone account selector count exceeds the KSP bound"); } self.accounts.push(account); return std::result::Result::Ok(()); } /// Adds one exact account-owner selector. pub fn push_owner(&mut self, owner: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { if self.owners.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT { return invalid_subscribe_parameter("grpc_subscribe.accounts.owner", "Yellowstone account owner count exceeds the KSP bound"); } self.owners.push(owner); return std::result::Result::Ok(()); } /// Adds one account predicate while preserving wire order. pub fn push_filter(&mut self, filter: crate::YellowstoneAccountFilterPredicate) -> ksp_core_lib::Result<()> { if self.filters.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_PREDICATE_COUNT { return invalid_subscribe_parameter("grpc_subscribe.accounts.filters", "Yellowstone account predicate count exceeds the KSP bound"); } self.filters.push(filter); return std::result::Result::Ok(()); } /// Sets or clears the `nonempty_txn_signature` selector. pub fn set_nonempty_txn_signature(&mut self, value: std::option::Option) { self.nonempty_txn_signature = value; } /// Sets or clears the compressed account selector. pub fn set_cuckoo_accounts_filter(&mut self, value: std::option::Option) { self.cuckoo_accounts_filter = value; } /// Returns exact account selectors in wire order. #[must_use] pub fn accounts(&self) -> &[ksp_core_lib::Pubkey] { return self.accounts.as_slice(); } /// Returns exact owner selectors in wire order. #[must_use] pub fn owners(&self) -> &[ksp_core_lib::Pubkey] { return self.owners.as_slice(); } /// Returns account predicates in wire order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneAccountFilterPredicate] { return self.filters.as_slice(); } /// Returns the optional transaction-signature presence selector. #[must_use] pub const fn nonempty_txn_signature(&self) -> std::option::Option { return self.nonempty_txn_signature; } /// Returns the optional compressed account selector. #[must_use] pub fn cuckoo_accounts_filter(&self) -> std::option::Option<&crate::YellowstoneCuckooFilter> { return self.cuckoo_accounts_filter.as_ref(); } fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts { account: self.accounts.iter().map(std::string::ToString::to_string).collect(), owner: self.owners.iter().map(std::string::ToString::to_string).collect(), filters: self.filters.iter().map(crate::grpc_subscribe::YellowstoneAccountFilterPredicate::to_wire).collect(), nonempty_txn_signature: self.nonempty_txn_signature, cuckoo_accounts_filter: self.cuckoo_accounts_filter.as_ref().map(crate::grpc_subscribe::YellowstoneCuckooFilter::to_wire), }; } } impl std::default::Default for YellowstoneSubscribeAccountFilter { fn default() -> Self { return Self::new(); } } impl std::fmt::Debug for YellowstoneSubscribeAccountFilter { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribeAccountFilter") .field("account_count", &self.accounts.len()) .field("owner_count", &self.owners.len()) .field("predicate_count", &self.filters.len()) .field("nonempty_txn_signature", &self.nonempty_txn_signature) .field("has_cuckoo_accounts_filter", &self.cuckoo_accounts_filter.is_some()) .finish(); } } /// Complete slot-family filter group for the standard Yellowstone subscribe surface. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct YellowstoneSubscribeSlotFilter { filter_by_commitment: std::option::Option, interslot_updates: std::option::Option, } impl YellowstoneSubscribeSlotFilter { /// Creates an empty slot filter group. #[must_use] pub const fn new() -> Self { return Self { filter_by_commitment: std::option::Option::None, interslot_updates: std::option::Option::None }; } /// Sets or clears the commitment-filtering flag. pub fn set_filter_by_commitment(&mut self, value: std::option::Option) { self.filter_by_commitment = value; } /// Sets or clears interslot status updates. pub fn set_interslot_updates(&mut self, value: std::option::Option) { self.interslot_updates = value; } /// Returns the optional commitment-filtering flag. #[must_use] pub const fn filter_by_commitment(self) -> std::option::Option { return self.filter_by_commitment; } /// Returns the optional interslot-update flag. #[must_use] pub const fn interslot_updates(self) -> std::option::Option { return self.interslot_updates; } fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots { filter_by_commitment: self.filter_by_commitment, interslot_updates: self.interslot_updates, }; } } /// Timestamp attached to one standard Yellowstone update envelope. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct YellowstoneUpdateTimestamp { seconds: i64, nanos: u32, } impl YellowstoneUpdateTimestamp { /// Creates one validated protobuf-compatible timestamp. pub fn new(seconds: i64, nanos: u32) -> ksp_core_lib::Result { if nanos >= 1_000_000_000 { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "Yellowstone update timestamp has invalid nanoseconds") .with_context("grpc_update", "timestamp"), ); } return std::result::Result::Ok(Self { seconds, nanos }); } /// Returns Unix seconds. #[must_use] pub const fn seconds(self) -> i64 { return self.seconds; } /// Returns sub-second nanoseconds. #[must_use] pub const fn nanos(self) -> u32 { return self.nanos; } } /// Fixed-width transaction signature attached to a Yellowstone account update when available. #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct YellowstoneTransactionSignature { bytes: [u8; 64], } impl YellowstoneTransactionSignature { /// Creates one fixed-width signature from exact bytes. #[must_use] pub const fn new(bytes: [u8; 64]) -> Self { return Self { bytes }; } /// Returns the exact 64 signature bytes. #[must_use] pub const fn as_bytes(&self) -> &[u8; 64] { return &self.bytes; } } impl std::fmt::Debug for YellowstoneTransactionSignature { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("YellowstoneTransactionSignature()"); } } /// Typed account payload carried by one standard Yellowstone account update. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneAccountInfo { pubkey: ksp_core_lib::Pubkey, lamports: u64, owner: ksp_core_lib::Pubkey, executable: bool, rent_epoch: u64, data: std::vec::Vec, write_version: u64, transaction_signature: std::option::Option, } impl YellowstoneAccountInfo { /// Returns the account public key. #[must_use] pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey { return &self.pubkey; } /// Returns the lamport balance. #[must_use] pub const fn lamports(&self) -> u64 { return self.lamports; } /// Returns the owner public key. #[must_use] pub const fn owner(&self) -> &ksp_core_lib::Pubkey { return &self.owner; } /// Returns whether the account is executable. #[must_use] pub const fn executable(&self) -> bool { return self.executable; } /// Returns the rent epoch. #[must_use] pub const fn rent_epoch(&self) -> u64 { return self.rent_epoch; } /// Returns the account bytes after any request-side `accounts_data_slice` has been applied by the server. #[must_use] pub fn data(&self) -> &[u8] { return self.data.as_slice(); } /// Returns the account write version. #[must_use] pub const fn write_version(&self) -> u64 { return self.write_version; } /// Returns the optional transaction signature associated with this update. #[must_use] pub const fn transaction_signature(&self) -> std::option::Option { return self.transaction_signature; } } impl std::fmt::Debug for YellowstoneAccountInfo { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneAccountInfo") .field("lamports", &self.lamports) .field("executable", &self.executable) .field("rent_epoch", &self.rent_epoch) .field("data_length", &self.data.len()) .field("write_version", &self.write_version) .field("has_transaction_signature", &self.transaction_signature.is_some()) .finish_non_exhaustive(); } } /// Standard Yellowstone account update projected into KSP-owned types. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneAccountUpdate { filters: std::vec::Vec, created_at: std::option::Option, account: crate::YellowstoneAccountInfo, slot: u64, is_startup: bool, } impl YellowstoneAccountUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns the optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the typed account payload. #[must_use] pub const fn account(&self) -> &crate::YellowstoneAccountInfo { return &self.account; } /// Returns the slot carrying the account update. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns whether the update belongs to startup/replay state reported by the server. #[must_use] pub const fn is_startup(&self) -> bool { return self.is_startup; } } impl std::fmt::Debug for YellowstoneAccountUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneAccountUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("account", &self.account) .field("slot", &self.slot) .field("is_startup", &self.is_startup) .finish(); } } /// Slot status published by current standard Yellowstone updates. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneSlotStatus { /// Slot reached processed commitment. Processed, /// Slot reached confirmed commitment. Confirmed, /// Slot reached finalized commitment. Finalized, /// First shred for the slot was received. FirstShredReceived, /// Slot ingestion completed. Completed, /// A bank was created for the slot. CreatedBank, /// Slot was marked dead. Dead, } /// Standard Yellowstone slot update projected into KSP-owned types. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSlotUpdate { filters: std::vec::Vec, created_at: std::option::Option, slot: u64, parent: std::option::Option, status: crate::YellowstoneSlotStatus, dead_error: std::option::Option, } impl YellowstoneSlotUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns the optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the slot number. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns the optional parent slot exactly as published. #[must_use] pub const fn parent(&self) -> std::option::Option { return self.parent; } /// Returns the current Yellowstone slot status. #[must_use] pub const fn status(&self) -> crate::YellowstoneSlotStatus { return self.status; } /// Returns the bounded server dead-slot diagnostic when present. #[must_use] pub fn dead_error(&self) -> std::option::Option<&str> { return self.dead_error.as_deref(); } } impl std::fmt::Debug for YellowstoneSlotUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSlotUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("slot", &self.slot) .field("parent", &self.parent) .field("status", &self.status) .field("has_dead_error", &self.dead_error.is_some()) .finish(); } } /// Optional token-account owner expansion applied by current Yellowstone transaction filters. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneTokenAccountExpansion { /// Match owners present in either pre- or post-token balances. All, /// Match owners whose token balance changed or whose token account was closed. BalanceChanged, } impl YellowstoneTokenAccountExpansion { const fn to_wire(self) -> i32 { return match self { Self::All => yellowstone_grpc_proto::geyser::TokenAccountExpansionControlFlag::All as i32, Self::BalanceChanged => yellowstone_grpc_proto::geyser::TokenAccountExpansionControlFlag::BalanceChanged as i32, }; } } /// Validated base58 transaction-signature selector used by Yellowstone transaction filters. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneTransactionSignatureSelector { value: std::string::String, } impl YellowstoneTransactionSignatureSelector { /// Validates one non-empty, bounded base58 signature selector without logging its value. pub fn new(value: impl std::convert::Into) -> ksp_core_lib::Result { let value = value.into(); if value.is_empty() || value.len() > MAX_GRPC_SUBSCRIBE_TRANSACTION_SIGNATURE_TEXT_LENGTH_BYTES || base58_decoded_length(value.as_str()) != std::option::Option::Some(YELLOWSTONE_TRANSACTION_SIGNATURE_WIRE_LENGTH_BYTES) { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone transaction signature selector is invalid") .with_context("field", "grpc_subscribe.transaction.signature"), ); } return std::result::Result::Ok(Self { value }); } /// Returns the validated signature text. Treat this value as caller-provided filter material. #[must_use] pub fn as_str(&self) -> &str { return self.value.as_str(); } } impl std::fmt::Debug for YellowstoneTransactionSignatureSelector { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("YellowstoneTransactionSignatureSelector()"); } } /// Complete current transaction-family filter shared by `transactions` and `transactions_status`. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribeTransactionFilter { vote: std::option::Option, failed: std::option::Option, signature: std::option::Option, account_include: std::vec::Vec, account_exclude: std::vec::Vec, account_required: std::vec::Vec, cuckoo_account_include: std::option::Option, token_accounts: std::option::Option, } impl YellowstoneSubscribeTransactionFilter { /// Creates an empty transaction filter group. #[must_use] pub fn new() -> Self { return Self { vote: std::option::Option::None, failed: std::option::Option::None, signature: std::option::Option::None, account_include: std::vec::Vec::new(), account_exclude: std::vec::Vec::new(), account_required: std::vec::Vec::new(), cuckoo_account_include: std::option::Option::None, token_accounts: std::option::Option::None, }; } /// Sets or clears the optional vote-transaction selector. pub fn set_vote(&mut self, value: std::option::Option) { self.vote = value; } /// Sets or clears the optional failed-transaction selector. pub fn set_failed(&mut self, value: std::option::Option) { self.failed = value; } /// Sets or clears the exact transaction-signature selector. pub fn set_signature(&mut self, value: std::option::Option) { self.signature = value; } /// Adds one account include selector while preserving insertion order. pub fn push_account_include(&mut self, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { return push_transaction_selector(&mut self.account_include, value, "grpc_subscribe.transaction.account_include"); } /// Adds one account exclude selector while preserving insertion order. pub fn push_account_exclude(&mut self, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { return push_transaction_selector(&mut self.account_exclude, value, "grpc_subscribe.transaction.account_exclude"); } /// Adds one required account selector while preserving insertion order. pub fn push_account_required(&mut self, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { return push_transaction_selector(&mut self.account_required, value, "grpc_subscribe.transaction.account_required"); } /// Sets or clears the optional compressed account-include filter. pub fn set_cuckoo_account_include(&mut self, value: std::option::Option) { self.cuckoo_account_include = value; } /// Sets or clears token-account owner expansion. pub fn set_token_accounts(&mut self, value: std::option::Option) { self.token_accounts = value; } /// Returns the optional vote selector. #[must_use] pub const fn vote(&self) -> std::option::Option { return self.vote; } /// Returns the optional failed selector. #[must_use] pub const fn failed(&self) -> std::option::Option { return self.failed; } /// Returns the optional exact signature selector. #[must_use] pub const fn signature(&self) -> std::option::Option<&crate::YellowstoneTransactionSignatureSelector> { return self.signature.as_ref(); } /// Returns ordered account include selectors. #[must_use] pub fn account_include(&self) -> &[ksp_core_lib::Pubkey] { return self.account_include.as_slice(); } /// Returns ordered account exclude selectors. #[must_use] pub fn account_exclude(&self) -> &[ksp_core_lib::Pubkey] { return self.account_exclude.as_slice(); } /// Returns ordered required account selectors. #[must_use] pub fn account_required(&self) -> &[ksp_core_lib::Pubkey] { return self.account_required.as_slice(); } /// Returns the optional compressed include filter. #[must_use] pub const fn cuckoo_account_include(&self) -> std::option::Option<&crate::YellowstoneCuckooFilter> { return self.cuckoo_account_include.as_ref(); } /// Returns optional token-account owner expansion. #[must_use] pub const fn token_accounts(&self) -> std::option::Option { return self.token_accounts; } fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions { vote: self.vote, failed: self.failed, signature: self.signature.as_ref().map(|value| return value.as_str().to_owned()), account_include: self.account_include.iter().map(std::string::ToString::to_string).collect(), account_exclude: self.account_exclude.iter().map(std::string::ToString::to_string).collect(), account_required: self.account_required.iter().map(std::string::ToString::to_string).collect(), cuckoo_account_include: self.cuckoo_account_include.as_ref().map(crate::YellowstoneCuckooFilter::to_wire), token_accounts: self.token_accounts.map(crate::YellowstoneTokenAccountExpansion::to_wire), }; } } impl std::default::Default for YellowstoneSubscribeTransactionFilter { fn default() -> Self { return Self::new(); } } impl std::fmt::Debug for YellowstoneSubscribeTransactionFilter { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribeTransactionFilter") .field("vote", &self.vote) .field("failed", &self.failed) .field("has_signature", &self.signature.is_some()) .field("account_include_count", &self.account_include.len()) .field("account_exclude_count", &self.account_exclude.len()) .field("account_required_count", &self.account_required.len()) .field("has_cuckoo_account_include", &self.cuckoo_account_include.is_some()) .field("token_accounts", &self.token_accounts) .finish(); } } /// Fixed-width 32-byte hash used by the Solana storage protobuf transaction wire. #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct YellowstoneHashBytes { bytes: [u8; 32], } impl YellowstoneHashBytes { /// Returns the exact 32 hash bytes. #[must_use] pub const fn as_bytes(&self) -> &[u8; 32] { return &self.bytes; } } impl std::fmt::Debug for YellowstoneHashBytes { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("YellowstoneHashBytes()"); } } /// Opaque runtime transaction error bytes carried by `solana-storage.proto`. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneTransactionError { bytes: std::vec::Vec, } impl YellowstoneTransactionError { /// Returns the exact opaque error bytes without interpreting runtime/program semantics. #[must_use] pub fn as_bytes(&self) -> &[u8] { return self.bytes.as_slice(); } } impl std::fmt::Debug for YellowstoneTransactionError { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.debug_struct("YellowstoneTransactionError").field("length", &self.bytes.len()).finish(); } } /// Solana transaction message header projected from the storage protobuf. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct YellowstoneTransactionMessageHeader { num_required_signatures: u32, num_readonly_signed_accounts: u32, num_readonly_unsigned_accounts: u32, } impl YellowstoneTransactionMessageHeader { /// Returns the required signature count. #[must_use] pub const fn num_required_signatures(self) -> u32 { return self.num_required_signatures; } /// Returns the readonly signed-account count. #[must_use] pub const fn num_readonly_signed_accounts(self) -> u32 { return self.num_readonly_signed_accounts; } /// Returns the readonly unsigned-account count. #[must_use] pub const fn num_readonly_unsigned_accounts(self) -> u32 { return self.num_readonly_unsigned_accounts; } } /// One compiled instruction from the Solana storage protobuf transaction message. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneCompiledInstruction { program_id_index: u32, accounts: std::vec::Vec, data: std::vec::Vec, } impl YellowstoneCompiledInstruction { /// Returns the program-id index. #[must_use] pub const fn program_id_index(&self) -> u32 { return self.program_id_index; } /// Returns the exact account-index bytes. #[must_use] pub fn accounts(&self) -> &[u8] { return self.accounts.as_slice(); } /// Returns the exact instruction data bytes. #[must_use] pub fn data(&self) -> &[u8] { return self.data.as_slice(); } } impl std::fmt::Debug for YellowstoneCompiledInstruction { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneCompiledInstruction") .field("program_id_index", &self.program_id_index) .field("account_index_count", &self.accounts.len()) .field("data_length", &self.data.len()) .finish(); } } /// One address-table lookup from a versioned Solana transaction message. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneMessageAddressTableLookup { account_key: ksp_core_lib::Pubkey, writable_indexes: std::vec::Vec, readonly_indexes: std::vec::Vec, } impl YellowstoneMessageAddressTableLookup { /// Returns the lookup-table account key. #[must_use] pub const fn account_key(&self) -> &ksp_core_lib::Pubkey { return &self.account_key; } /// Returns ordered writable lookup indexes. #[must_use] pub fn writable_indexes(&self) -> &[u8] { return self.writable_indexes.as_slice(); } /// Returns ordered readonly lookup indexes. #[must_use] pub fn readonly_indexes(&self) -> &[u8] { return self.readonly_indexes.as_slice(); } } impl std::fmt::Debug for YellowstoneMessageAddressTableLookup { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneMessageAddressTableLookup") .field("writable_index_count", &self.writable_indexes.len()) .field("readonly_index_count", &self.readonly_indexes.len()) .finish_non_exhaustive(); } } /// Optional inline budget configuration introduced by Solana Transaction V1 / SIMD-0385. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct YellowstoneTransactionConfig { priority_fee: std::option::Option, compute_unit_limit: std::option::Option, loaded_accounts_data_size_limit: std::option::Option, heap_size: std::option::Option, } impl YellowstoneTransactionConfig { /// Returns the optional priority fee. #[must_use] pub const fn priority_fee(self) -> std::option::Option { return self.priority_fee; } /// Returns the optional compute-unit limit. #[must_use] pub const fn compute_unit_limit(self) -> std::option::Option { return self.compute_unit_limit; } /// Returns the optional loaded-account-data-size limit. #[must_use] pub const fn loaded_accounts_data_size_limit(self) -> std::option::Option { return self.loaded_accounts_data_size_limit; } /// Returns the optional heap-size override. #[must_use] pub const fn heap_size(self) -> std::option::Option { return self.heap_size; } } /// Complete Solana transaction message projected from current `solana-storage.proto`. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneTransactionMessage { header: crate::YellowstoneTransactionMessageHeader, account_keys: std::vec::Vec, recent_blockhash: crate::YellowstoneHashBytes, instructions: std::vec::Vec, versioned: bool, address_table_lookups: std::vec::Vec, config: std::option::Option, } impl YellowstoneTransactionMessage { /// Returns the message header. #[must_use] pub const fn header(&self) -> crate::YellowstoneTransactionMessageHeader { return self.header; } /// Returns ordered static account keys. #[must_use] pub fn account_keys(&self) -> &[ksp_core_lib::Pubkey] { return self.account_keys.as_slice(); } /// Returns the recent blockhash bytes. #[must_use] pub const fn recent_blockhash(&self) -> crate::YellowstoneHashBytes { return self.recent_blockhash; } /// Returns ordered compiled instructions. #[must_use] pub fn instructions(&self) -> &[crate::YellowstoneCompiledInstruction] { return self.instructions.as_slice(); } /// Returns the protobuf `versioned` marker. #[must_use] pub const fn versioned(&self) -> bool { return self.versioned; } /// Returns ordered address-table lookups. #[must_use] pub fn address_table_lookups(&self) -> &[crate::YellowstoneMessageAddressTableLookup] { return self.address_table_lookups.as_slice(); } /// Returns optional Transaction V1 inline budget configuration. #[must_use] pub const fn config(&self) -> std::option::Option { return self.config; } } impl std::fmt::Debug for YellowstoneTransactionMessage { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTransactionMessage") .field("header", &self.header) .field("account_key_count", &self.account_keys.len()) .field("instruction_count", &self.instructions.len()) .field("versioned", &self.versioned) .field("address_table_lookup_count", &self.address_table_lookups.len()) .field("config", &self.config) .finish(); } } /// Solana transaction body carried by Yellowstone storage protobuf messages. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneStoredTransaction { signatures: std::vec::Vec, message: crate::YellowstoneTransactionMessage, } impl YellowstoneStoredTransaction { /// Returns ordered transaction signatures. #[must_use] pub fn signatures(&self) -> &[crate::YellowstoneTransactionSignature] { return self.signatures.as_slice(); } /// Returns the decoded transaction message. #[must_use] pub const fn message(&self) -> &crate::YellowstoneTransactionMessage { return &self.message; } } impl std::fmt::Debug for YellowstoneStoredTransaction { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneStoredTransaction") .field("signature_count", &self.signatures.len()) .field("message", &self.message) .finish(); } } /// One inner instruction from transaction status metadata. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneInnerInstruction { program_id_index: u32, accounts: std::vec::Vec, data: std::vec::Vec, stack_height: std::option::Option, } impl YellowstoneInnerInstruction { /// Returns the program-id index. #[must_use] pub const fn program_id_index(&self) -> u32 { return self.program_id_index; } /// Returns account-index bytes. #[must_use] pub fn accounts(&self) -> &[u8] { return self.accounts.as_slice(); } /// Returns instruction data bytes. #[must_use] pub fn data(&self) -> &[u8] { return self.data.as_slice(); } /// Returns optional invocation stack height. #[must_use] pub const fn stack_height(&self) -> std::option::Option { return self.stack_height; } } impl std::fmt::Debug for YellowstoneInnerInstruction { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneInnerInstruction") .field("program_id_index", &self.program_id_index) .field("account_index_count", &self.accounts.len()) .field("data_length", &self.data.len()) .field("stack_height", &self.stack_height) .finish(); } } /// One indexed inner-instruction group from transaction status metadata. #[derive(Clone, Debug, Eq, PartialEq)] pub struct YellowstoneInnerInstructions { index: u32, instructions: std::vec::Vec, } impl YellowstoneInnerInstructions { /// Returns the outer instruction index. #[must_use] pub const fn index(&self) -> u32 { return self.index; } /// Returns ordered inner instructions. #[must_use] pub fn instructions(&self) -> &[crate::YellowstoneInnerInstruction] { return self.instructions.as_slice(); } } /// UI token amount from current Solana storage protobuf metadata. #[derive(Clone, PartialEq)] pub struct YellowstoneUiTokenAmount { ui_amount: f64, decimals: u32, amount: std::string::String, ui_amount_string: std::string::String, } impl YellowstoneUiTokenAmount { /// Returns the floating UI amount carried by the protobuf. #[must_use] pub const fn ui_amount(&self) -> f64 { return self.ui_amount; } /// Returns token decimals. #[must_use] pub const fn decimals(&self) -> u32 { return self.decimals; } /// Returns the exact integer amount string. #[must_use] pub fn amount(&self) -> &str { return self.amount.as_str(); } /// Returns the exact UI amount string. #[must_use] pub fn ui_amount_string(&self) -> &str { return self.ui_amount_string.as_str(); } } impl std::fmt::Debug for YellowstoneUiTokenAmount { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneUiTokenAmount") .field("decimals", &self.decimals) .field("has_amount", &!self.amount.is_empty()) .field("has_ui_amount_string", &!self.ui_amount_string.is_empty()) .finish_non_exhaustive(); } } /// One pre/post token balance entry from transaction status metadata. #[derive(Clone, PartialEq)] pub struct YellowstoneTokenBalance { account_index: u32, mint: std::string::String, ui_token_amount: std::option::Option, owner: std::string::String, program_id: std::string::String, } impl YellowstoneTokenBalance { /// Returns the account index. #[must_use] pub const fn account_index(&self) -> u32 { return self.account_index; } /// Returns the exact mint text. #[must_use] pub fn mint(&self) -> &str { return self.mint.as_str(); } /// Returns optional UI token amount data exactly as represented by protobuf message presence. #[must_use] pub const fn ui_token_amount(&self) -> std::option::Option<&crate::YellowstoneUiTokenAmount> { return self.ui_token_amount.as_ref(); } /// Returns the exact owner text, including an empty legacy value when present on the wire. #[must_use] pub fn owner(&self) -> &str { return self.owner.as_str(); } /// Returns the exact token-program id text, including an empty legacy value. #[must_use] pub fn program_id(&self) -> &str { return self.program_id.as_str(); } } impl std::fmt::Debug for YellowstoneTokenBalance { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTokenBalance") .field("account_index", &self.account_index) .field("has_ui_token_amount", &self.ui_token_amount.is_some()) .field("has_owner", &!self.owner.is_empty()) .field("has_program_id", &!self.program_id.is_empty()) .finish_non_exhaustive(); } } /// Return-data payload from transaction status metadata. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneReturnData { program_id: ksp_core_lib::Pubkey, data: std::vec::Vec, } impl YellowstoneReturnData { /// Returns the program id that produced the return data. #[must_use] pub const fn program_id(&self) -> &ksp_core_lib::Pubkey { return &self.program_id; } /// Returns the exact return bytes. #[must_use] pub fn data(&self) -> &[u8] { return self.data.as_slice(); } } impl std::fmt::Debug for YellowstoneReturnData { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.debug_struct("YellowstoneReturnData").field("data_length", &self.data.len()).finish_non_exhaustive(); } } /// Reward classification carried by current Solana storage protobuf metadata. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum YellowstoneRewardType { /// Unspecified reward type. Unspecified, /// Fee reward. Fee, /// Rent reward. Rent, /// Staking reward. Staking, /// Voting reward. Voting, /// Deactivated stake reward. DeactivatedStake, /// Explicitly preserved future/unknown numeric value. Unknown(i32), } /// One reward entry from transaction status metadata. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneReward { pubkey: ksp_core_lib::Pubkey, lamports: i64, post_balance: u64, reward_type: crate::YellowstoneRewardType, commission: std::string::String, commission_bps: std::string::String, } impl YellowstoneReward { /// Returns the reward account. #[must_use] pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey { return &self.pubkey; } /// Returns signed reward lamports. #[must_use] pub const fn lamports(&self) -> i64 { return self.lamports; } /// Returns post-reward balance. #[must_use] pub const fn post_balance(&self) -> u64 { return self.post_balance; } /// Returns reward type, preserving unknown numeric values explicitly. #[must_use] pub const fn reward_type(&self) -> crate::YellowstoneRewardType { return self.reward_type; } /// Returns legacy commission text exactly as carried on the protobuf wire. #[must_use] pub fn commission(&self) -> &str { return self.commission.as_str(); } /// Returns commission basis-point text exactly as carried on the protobuf wire. #[must_use] pub fn commission_bps(&self) -> &str { return self.commission_bps.as_str(); } } impl std::fmt::Debug for YellowstoneReward { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneReward") .field("lamports", &self.lamports) .field("post_balance", &self.post_balance) .field("reward_type", &self.reward_type) .field("has_commission", &!self.commission.is_empty()) .field("has_commission_bps", &!self.commission_bps.is_empty()) .finish_non_exhaustive(); } } /// Complete transaction status metadata projected from current `solana-storage.proto`. #[derive(Clone, PartialEq)] pub struct YellowstoneTransactionStatusMeta { error: std::option::Option, fee: u64, pre_balances: std::vec::Vec, post_balances: std::vec::Vec, inner_instructions: std::vec::Vec, inner_instructions_none: bool, log_messages: std::vec::Vec, log_messages_none: bool, pre_token_balances: std::vec::Vec, post_token_balances: std::vec::Vec, rewards: std::vec::Vec, loaded_writable_addresses: std::vec::Vec, loaded_readonly_addresses: std::vec::Vec, return_data: std::option::Option, return_data_none: bool, compute_units_consumed: std::option::Option, cost_units: std::option::Option, } impl YellowstoneTransactionStatusMeta { /// Returns optional opaque runtime error bytes. #[must_use] pub const fn error(&self) -> std::option::Option<&crate::YellowstoneTransactionError> { return self.error.as_ref(); } /// Returns transaction fee in lamports. #[must_use] pub const fn fee(&self) -> u64 { return self.fee; } /// Returns pre-transaction lamport balances. #[must_use] pub fn pre_balances(&self) -> &[u64] { return self.pre_balances.as_slice(); } /// Returns post-transaction lamport balances. #[must_use] pub fn post_balances(&self) -> &[u64] { return self.post_balances.as_slice(); } /// Returns inner-instruction groups. #[must_use] pub fn inner_instructions(&self) -> &[crate::YellowstoneInnerInstructions] { return self.inner_instructions.as_slice(); } /// Returns the explicit legacy `inner_instructions_none` marker. #[must_use] pub const fn inner_instructions_none(&self) -> bool { return self.inner_instructions_none; } /// Returns ordered runtime log messages. #[must_use] pub fn log_messages(&self) -> &[std::string::String] { return self.log_messages.as_slice(); } /// Returns the explicit legacy `log_messages_none` marker. #[must_use] pub const fn log_messages_none(&self) -> bool { return self.log_messages_none; } /// Returns pre-token balances. #[must_use] pub fn pre_token_balances(&self) -> &[crate::YellowstoneTokenBalance] { return self.pre_token_balances.as_slice(); } /// Returns post-token balances. #[must_use] pub fn post_token_balances(&self) -> &[crate::YellowstoneTokenBalance] { return self.post_token_balances.as_slice(); } /// Returns reward entries. #[must_use] pub fn rewards(&self) -> &[crate::YellowstoneReward] { return self.rewards.as_slice(); } /// Returns loaded writable addresses. #[must_use] pub fn loaded_writable_addresses(&self) -> &[ksp_core_lib::Pubkey] { return self.loaded_writable_addresses.as_slice(); } /// Returns loaded readonly addresses. #[must_use] pub fn loaded_readonly_addresses(&self) -> &[ksp_core_lib::Pubkey] { return self.loaded_readonly_addresses.as_slice(); } /// Returns optional return data while preserving the separate protobuf `return_data_none` marker. #[must_use] pub const fn return_data(&self) -> std::option::Option<&crate::YellowstoneReturnData> { return self.return_data.as_ref(); } /// Returns the explicit legacy `return_data_none` marker. #[must_use] pub const fn return_data_none(&self) -> bool { return self.return_data_none; } /// Returns optional compute units consumed. #[must_use] pub const fn compute_units_consumed(&self) -> std::option::Option { return self.compute_units_consumed; } /// Returns optional total transaction cost units. #[must_use] pub const fn cost_units(&self) -> std::option::Option { return self.cost_units; } } impl std::fmt::Debug for YellowstoneTransactionStatusMeta { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTransactionStatusMeta") .field("has_error", &self.error.is_some()) .field("fee", &self.fee) .field("pre_balance_count", &self.pre_balances.len()) .field("post_balance_count", &self.post_balances.len()) .field("inner_instruction_group_count", &self.inner_instructions.len()) .field("inner_instructions_none", &self.inner_instructions_none) .field("log_message_count", &self.log_messages.len()) .field("log_messages_none", &self.log_messages_none) .field("pre_token_balance_count", &self.pre_token_balances.len()) .field("post_token_balance_count", &self.post_token_balances.len()) .field("reward_count", &self.rewards.len()) .field("loaded_writable_address_count", &self.loaded_writable_addresses.len()) .field("loaded_readonly_address_count", &self.loaded_readonly_addresses.len()) .field("has_return_data", &self.return_data.is_some()) .field("return_data_none", &self.return_data_none) .field("compute_units_consumed", &self.compute_units_consumed) .field("cost_units", &self.cost_units) .finish(); } } /// Full transaction info carried by `SubscribeUpdateTransaction` and later reused by block updates. #[derive(Clone, PartialEq)] pub struct YellowstoneTransactionInfo { signature: crate::YellowstoneTransactionSignature, is_vote: bool, transaction: crate::YellowstoneStoredTransaction, meta: crate::YellowstoneTransactionStatusMeta, index: u64, } impl YellowstoneTransactionInfo { /// Returns the primary transaction signature. #[must_use] pub const fn signature(&self) -> crate::YellowstoneTransactionSignature { return self.signature; } /// Returns whether Yellowstone classifies this as a vote transaction. #[must_use] pub const fn is_vote(&self) -> bool { return self.is_vote; } /// Returns the complete typed transaction body. #[must_use] pub const fn transaction(&self) -> &crate::YellowstoneStoredTransaction { return &self.transaction; } /// Returns complete typed transaction status metadata. #[must_use] pub const fn meta(&self) -> &crate::YellowstoneTransactionStatusMeta { return &self.meta; } /// Returns the transaction index within the block. #[must_use] pub const fn index(&self) -> u64 { return self.index; } } impl std::fmt::Debug for YellowstoneTransactionInfo { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTransactionInfo") .field("is_vote", &self.is_vote) .field("transaction", &self.transaction) .field("meta", &self.meta) .field("index", &self.index) .finish_non_exhaustive(); } } /// Full standard Yellowstone transaction update projected into KSP-owned types. #[derive(Clone, PartialEq)] pub struct YellowstoneTransactionUpdate { filters: std::vec::Vec, created_at: std::option::Option, transaction: crate::YellowstoneTransactionInfo, slot: u64, } impl YellowstoneTransactionUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns complete transaction info. #[must_use] pub const fn transaction(&self) -> &crate::YellowstoneTransactionInfo { return &self.transaction; } /// Returns the slot containing the transaction. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } } impl std::fmt::Debug for YellowstoneTransactionUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTransactionUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("transaction", &self.transaction) .field("slot", &self.slot) .finish(); } } /// Lightweight standard Yellowstone transaction-status update. #[derive(Clone, PartialEq)] pub struct YellowstoneTransactionStatusUpdate { filters: std::vec::Vec, created_at: std::option::Option, slot: u64, signature: crate::YellowstoneTransactionSignature, is_vote: bool, index: u64, error: std::option::Option, } impl YellowstoneTransactionStatusUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the containing slot. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns the transaction signature. #[must_use] pub const fn signature(&self) -> crate::YellowstoneTransactionSignature { return self.signature; } /// Returns whether this is a vote transaction. #[must_use] pub const fn is_vote(&self) -> bool { return self.is_vote; } /// Returns the transaction index within the block. #[must_use] pub const fn index(&self) -> u64 { return self.index; } /// Returns optional opaque runtime error bytes. #[must_use] pub const fn error(&self) -> std::option::Option<&crate::YellowstoneTransactionError> { return self.error.as_ref(); } } impl std::fmt::Debug for YellowstoneTransactionStatusUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneTransactionStatusUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("slot", &self.slot) .field("is_vote", &self.is_vote) .field("index", &self.index) .field("has_error", &self.error.is_some()) .finish_non_exhaustive(); } } /// Complete current block-family filter for the standard Yellowstone subscribe surface. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribeBlockFilter { account_include: std::vec::Vec, include_transactions: std::option::Option, include_accounts: std::option::Option, include_entries: std::option::Option, cuckoo_account_include: std::option::Option, } impl YellowstoneSubscribeBlockFilter { /// Creates an empty block filter group. #[must_use] pub fn new() -> Self { return Self { account_include: std::vec::Vec::new(), include_transactions: std::option::Option::None, include_accounts: std::option::Option::None, include_entries: std::option::Option::None, cuckoo_account_include: std::option::Option::None, }; } /// Adds one account include selector while preserving insertion order. pub fn push_account_include(&mut self, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { return push_block_selector(&mut self.account_include, value); } /// Sets or clears whether full transaction payloads are included in matching block updates. pub fn set_include_transactions(&mut self, value: std::option::Option) { self.include_transactions = value; } /// Sets or clears whether account payloads are included in matching block updates. pub fn set_include_accounts(&mut self, value: std::option::Option) { self.include_accounts = value; } /// Sets or clears whether entry payloads are included in matching block updates. pub fn set_include_entries(&mut self, value: std::option::Option) { self.include_entries = value; } /// Sets or clears the optional compressed account-include filter. pub fn set_cuckoo_account_include(&mut self, value: std::option::Option) { self.cuckoo_account_include = value; } /// Returns ordered account include selectors. #[must_use] pub fn account_include(&self) -> &[ksp_core_lib::Pubkey] { return self.account_include.as_slice(); } /// Returns the optional transaction-payload inclusion flag. #[must_use] pub const fn include_transactions(&self) -> std::option::Option { return self.include_transactions; } /// Returns the optional account-payload inclusion flag. #[must_use] pub const fn include_accounts(&self) -> std::option::Option { return self.include_accounts; } /// Returns the optional entry-payload inclusion flag. #[must_use] pub const fn include_entries(&self) -> std::option::Option { return self.include_entries; } /// Returns the optional compressed account-include filter. #[must_use] pub const fn cuckoo_account_include(&self) -> std::option::Option<&crate::YellowstoneCuckooFilter> { return self.cuckoo_account_include.as_ref(); } fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks { account_include: self.account_include.iter().map(std::string::ToString::to_string).collect(), include_transactions: self.include_transactions, include_accounts: self.include_accounts, include_entries: self.include_entries, cuckoo_account_include: self.cuckoo_account_include.as_ref().map(crate::YellowstoneCuckooFilter::to_wire), }; } } impl std::default::Default for YellowstoneSubscribeBlockFilter { fn default() -> Self { return Self::new(); } } impl std::fmt::Debug for YellowstoneSubscribeBlockFilter { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribeBlockFilter") .field("account_include_count", &self.account_include.len()) .field("include_transactions", &self.include_transactions) .field("include_accounts", &self.include_accounts) .field("include_entries", &self.include_entries) .field("has_cuckoo_account_include", &self.cuckoo_account_include.is_some()) .finish(); } } /// Rewards container carried by Yellowstone block and block-meta updates. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneBlockRewards { rewards: std::vec::Vec, num_partitions: std::option::Option, } impl YellowstoneBlockRewards { /// Returns ordered reward entries. #[must_use] pub fn rewards(&self) -> &[crate::YellowstoneReward] { return self.rewards.as_slice(); } /// Returns the optional number of reward partitions represented by the block. #[must_use] pub const fn num_partitions(&self) -> std::option::Option { return self.num_partitions; } } impl std::fmt::Debug for YellowstoneBlockRewards { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneBlockRewards") .field("reward_count", &self.rewards.len()) .field("num_partitions", &self.num_partitions) .finish(); } } /// One Yellowstone block-entry payload. The same wire message is used standalone and inside full block updates. #[derive(Clone, Copy, Eq, PartialEq)] pub struct YellowstoneEntryInfo { slot: u64, index: u64, num_hashes: u64, hash: crate::YellowstoneHashBytes, executed_transaction_count: u64, starting_transaction_index: u64, } impl YellowstoneEntryInfo { /// Returns the containing slot. #[must_use] pub const fn slot(self) -> u64 { return self.slot; } /// Returns the entry index within the slot. #[must_use] pub const fn index(self) -> u64 { return self.index; } /// Returns the number of hashes represented by this entry. #[must_use] pub const fn num_hashes(self) -> u64 { return self.num_hashes; } /// Returns the exact fixed-width entry hash. #[must_use] pub const fn hash(self) -> crate::YellowstoneHashBytes { return self.hash; } /// Returns the number of transactions executed by this entry. #[must_use] pub const fn executed_transaction_count(self) -> u64 { return self.executed_transaction_count; } /// Returns the starting transaction index, including the legacy zero value emitted by older validators. #[must_use] pub const fn starting_transaction_index(self) -> u64 { return self.starting_transaction_index; } } impl std::fmt::Debug for YellowstoneEntryInfo { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneEntryInfo") .field("slot", &self.slot) .field("index", &self.index) .field("num_hashes", &self.num_hashes) .field("executed_transaction_count", &self.executed_transaction_count) .field("starting_transaction_index", &self.starting_transaction_index) .finish_non_exhaustive(); } } /// Full standard Yellowstone block update projected into KSP-owned types. #[derive(Clone, PartialEq)] pub struct YellowstoneBlockUpdate { filters: std::vec::Vec, created_at: std::option::Option, slot: u64, blockhash: std::string::String, rewards: std::option::Option, block_time: std::option::Option, block_height: std::option::Option, parent_slot: u64, parent_blockhash: std::string::String, executed_transaction_count: u64, transactions: std::vec::Vec, updated_account_count: u64, accounts: std::vec::Vec, entries_count: u64, entries: std::vec::Vec, } impl YellowstoneBlockUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the block slot. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns the exact blockhash text validated as a 32-byte Base58 Solana hash. #[must_use] pub fn blockhash(&self) -> &str { return self.blockhash.as_str(); } /// Returns optional block rewards. #[must_use] pub const fn rewards(&self) -> std::option::Option<&crate::YellowstoneBlockRewards> { return self.rewards.as_ref(); } /// Returns optional Unix block time. #[must_use] pub const fn block_time(&self) -> std::option::Option { return self.block_time; } /// Returns optional block height. #[must_use] pub const fn block_height(&self) -> std::option::Option { return self.block_height; } /// Returns the parent slot. #[must_use] pub const fn parent_slot(&self) -> u64 { return self.parent_slot; } /// Returns the exact parent blockhash text. #[must_use] pub fn parent_blockhash(&self) -> &str { return self.parent_blockhash.as_str(); } /// Returns the server-reported executed transaction count independently of whether transaction payloads were requested. #[must_use] pub const fn executed_transaction_count(&self) -> u64 { return self.executed_transaction_count; } /// Returns transaction payloads included by the server. #[must_use] pub fn transactions(&self) -> &[crate::YellowstoneTransactionInfo] { return self.transactions.as_slice(); } /// Returns the server-reported updated account count independently of whether account payloads were requested. #[must_use] pub const fn updated_account_count(&self) -> u64 { return self.updated_account_count; } /// Returns account payloads included by the server. #[must_use] pub fn accounts(&self) -> &[crate::YellowstoneAccountInfo] { return self.accounts.as_slice(); } /// Returns the server-reported entry count independently of whether entry payloads were requested. #[must_use] pub const fn entries_count(&self) -> u64 { return self.entries_count; } /// Returns entry payloads included by the server. #[must_use] pub fn entries(&self) -> &[crate::YellowstoneEntryInfo] { return self.entries.as_slice(); } } impl std::fmt::Debug for YellowstoneBlockUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneBlockUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("slot", &self.slot) .field("has_rewards", &self.rewards.is_some()) .field("block_time", &self.block_time) .field("block_height", &self.block_height) .field("parent_slot", &self.parent_slot) .field("executed_transaction_count", &self.executed_transaction_count) .field("transaction_payload_count", &self.transactions.len()) .field("updated_account_count", &self.updated_account_count) .field("account_payload_count", &self.accounts.len()) .field("entries_count", &self.entries_count) .field("entry_payload_count", &self.entries.len()) .finish_non_exhaustive(); } } /// Metadata-only standard Yellowstone block update. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneBlockMetaUpdate { filters: std::vec::Vec, created_at: std::option::Option, slot: u64, blockhash: std::string::String, rewards: std::option::Option, block_time: std::option::Option, block_height: std::option::Option, parent_slot: u64, parent_blockhash: std::string::String, executed_transaction_count: u64, entries_count: u64, } impl YellowstoneBlockMetaUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the block slot. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns the exact blockhash text. #[must_use] pub fn blockhash(&self) -> &str { return self.blockhash.as_str(); } /// Returns optional block rewards. #[must_use] pub const fn rewards(&self) -> std::option::Option<&crate::YellowstoneBlockRewards> { return self.rewards.as_ref(); } /// Returns optional Unix block time. #[must_use] pub const fn block_time(&self) -> std::option::Option { return self.block_time; } /// Returns optional block height. #[must_use] pub const fn block_height(&self) -> std::option::Option { return self.block_height; } /// Returns the parent slot. #[must_use] pub const fn parent_slot(&self) -> u64 { return self.parent_slot; } /// Returns the exact parent blockhash text. #[must_use] pub fn parent_blockhash(&self) -> &str { return self.parent_blockhash.as_str(); } /// Returns the server-reported executed transaction count. #[must_use] pub const fn executed_transaction_count(&self) -> u64 { return self.executed_transaction_count; } /// Returns the server-reported entry count. #[must_use] pub const fn entries_count(&self) -> u64 { return self.entries_count; } } impl std::fmt::Debug for YellowstoneBlockMetaUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneBlockMetaUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("slot", &self.slot) .field("has_rewards", &self.rewards.is_some()) .field("block_time", &self.block_time) .field("block_height", &self.block_height) .field("parent_slot", &self.parent_slot) .field("executed_transaction_count", &self.executed_transaction_count) .field("entries_count", &self.entries_count) .finish_non_exhaustive(); } } /// Standalone standard Yellowstone entry update with its update envelope. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneEntryUpdate { filters: std::vec::Vec, created_at: std::option::Option, entry: crate::YellowstoneEntryInfo, } impl YellowstoneEntryUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the complete entry payload. #[must_use] pub const fn entry(&self) -> crate::YellowstoneEntryInfo { return self.entry; } } impl std::fmt::Debug for YellowstoneEntryUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneEntryUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("entry", &self.entry) .finish(); } } /// Server-side Yellowstone keepalive ping update. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribePingUpdate { filters: std::vec::Vec, created_at: std::option::Option, } impl YellowstoneSubscribePingUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns the optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } } impl std::fmt::Debug for YellowstoneSubscribePingUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribePingUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .finish(); } } /// Server-side Yellowstone keepalive pong update. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribePongUpdate { filters: std::vec::Vec, created_at: std::option::Option, id: i32, } impl YellowstoneSubscribePongUpdate { /// Returns echoed matching filter names in server order. #[must_use] pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] { return self.filters.as_slice(); } /// Returns the optional server creation timestamp. #[must_use] pub const fn created_at(&self) -> std::option::Option { return self.created_at; } /// Returns the exact ping identifier echoed by the server. #[must_use] pub const fn id(&self) -> i32 { return self.id; } } impl std::fmt::Debug for YellowstoneSubscribePongUpdate { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribePongUpdate") .field("filter_count", &self.filters.len()) .field("created_at", &self.created_at) .field("id", &self.id) .finish(); } } /// Any standard Yellowstone update delivered by the bidirectional `Subscribe` stream. #[derive(Clone, Debug, PartialEq)] pub enum YellowstoneSubscribeUpdate { /// Account update. Account(crate::YellowstoneAccountUpdate), /// Slot lifecycle update. Slot(crate::YellowstoneSlotUpdate), /// Full transaction update. Transaction(std::boxed::Box), /// Lightweight transaction-status update. TransactionStatus(crate::YellowstoneTransactionStatusUpdate), /// Full block update. Block(crate::YellowstoneBlockUpdate), /// Server keepalive ping. KSP replies automatically with a ping request. Ping(crate::YellowstoneSubscribePingUpdate), /// Server keepalive pong. Pong(crate::YellowstoneSubscribePongUpdate), /// Block-metadata update. BlockMeta(crate::YellowstoneBlockMetaUpdate), /// Entry update. Entry(crate::YellowstoneEntryUpdate), } /// Named empty filter activating the standard Yellowstone `blocks_meta` family. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct YellowstoneSubscribeBlocksMetaFilter { _private: (), } impl YellowstoneSubscribeBlocksMetaFilter { /// Creates the empty marker filter. #[must_use] pub const fn new() -> Self { return Self { _private: () }; } fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocksMeta { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocksMeta::default(); } } /// Named empty filter activating the standard Yellowstone `entry` family. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct YellowstoneSubscribeEntryFilter { _private: (), } impl YellowstoneSubscribeEntryFilter { /// Creates the empty marker filter. #[must_use] pub const fn new() -> Self { return Self { _private: () }; } fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterEntry { return yellowstone_grpc_proto::geyser::SubscribeRequestFilterEntry::default(); } } /// Provider-neutral standard Yellowstone subscribe request owned by KSP. /// /// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active /// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven /// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or /// future /// filter payloads. #[derive(Clone, Eq, PartialEq)] pub struct YellowstoneSubscribeRequest { accounts: std::collections::BTreeMap, slots: std::collections::BTreeMap, transactions: std::collections::BTreeMap, transactions_status: std::collections::BTreeMap, blocks: std::collections::BTreeMap, blocks_meta: std::collections::BTreeMap, entry: std::collections::BTreeMap, commitment: std::option::Option, accounts_data_slice: std::vec::Vec, ping: std::option::Option, from_slot: std::option::Option, } impl YellowstoneSubscribeRequest { /// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping /// state. #[must_use] pub fn new() -> Self { return Self { accounts: std::collections::BTreeMap::new(), slots: std::collections::BTreeMap::new(), transactions: std::collections::BTreeMap::new(), transactions_status: std::collections::BTreeMap::new(), blocks: std::collections::BTreeMap::new(), blocks_meta: std::collections::BTreeMap::new(), entry: std::collections::BTreeMap::new(), commitment: std::option::Option::None, accounts_data_slice: std::vec::Vec::new(), ping: std::option::Option::None, from_slot: std::option::Option::None, }; } /// Inserts one named account filter group. pub fn insert_account_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeAccountFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.accounts.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named slot filter group. pub fn insert_slot_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeSlotFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.slots.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named transaction filter group. pub fn insert_transaction_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeTransactionFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.transactions.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named transaction-status filter group. pub fn insert_transaction_status_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeTransactionFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.transactions_status.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named block filter group. pub fn insert_block_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeBlockFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.blocks.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named empty `blocks_meta` filter group. pub fn insert_blocks_meta_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeBlocksMetaFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.blocks_meta.insert(name, filter); return std::result::Result::Ok(()); } /// Inserts one named empty `entry` filter group. pub fn insert_entry_filter( &mut self, name: crate::YellowstoneSubscribeFilterName, filter: crate::YellowstoneSubscribeEntryFilter, ) -> ksp_core_lib::Result<()> { let validation = self.validate_new_filter_name(&name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } self.entry.insert(name, filter); return std::result::Result::Ok(()); } /// Sets or clears the optional standard Yellowstone commitment. pub fn set_commitment(&mut self, commitment: std::option::Option) { self.commitment = commitment; } /// Adds one validated account-data slice while preserving insertion order. pub fn push_accounts_data_slice(&mut self, slice: crate::YellowstoneAccountsDataSlice) -> ksp_core_lib::Result<()> { if self.accounts_data_slice.len() >= MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice count exceeds KSP bounds") .with_context("field", "grpc_subscribe.accounts_data_slice"), ); } self.accounts_data_slice.push(slice); return std::result::Result::Ok(()); } /// Sets or clears the optional subscribe-stream ping mutation. pub fn set_ping(&mut self, ping: std::option::Option) { self.ping = ping; } /// Sets or clears the optional replay starting slot. pub fn set_from_slot(&mut self, from_slot: std::option::Option) { self.from_slot = from_slot; } /// Returns the number of active account filter groups. #[must_use] pub fn account_filter_count(&self) -> usize { return self.accounts.len(); } /// Returns the number of active slot filter groups. #[must_use] pub fn slot_filter_count(&self) -> usize { return self.slots.len(); } /// Returns the number of active transaction filter groups. #[must_use] pub fn transaction_filter_count(&self) -> usize { return self.transactions.len(); } /// Returns the number of active transaction-status filter groups. #[must_use] pub fn transaction_status_filter_count(&self) -> usize { return self.transactions_status.len(); } /// Returns the number of active block filter groups. #[must_use] pub fn block_filter_count(&self) -> usize { return self.blocks.len(); } /// Returns the number of active block-meta filter groups. #[must_use] pub fn blocks_meta_filter_count(&self) -> usize { return self.blocks_meta.len(); } /// Returns the number of active entry filter groups. #[must_use] pub fn entry_filter_count(&self) -> usize { return self.entry.len(); } /// Returns the optional request commitment. #[must_use] pub const fn commitment(&self) -> std::option::Option { return self.commitment; } /// Returns account-data slices in exact wire order. #[must_use] pub fn accounts_data_slices(&self) -> &[crate::YellowstoneAccountsDataSlice] { return self.accounts_data_slice.as_slice(); } /// Returns the optional subscribe-stream ping mutation. #[must_use] pub const fn ping(&self) -> std::option::Option { return self.ping; } /// Returns the optional replay starting slot. #[must_use] pub const fn from_slot(&self) -> std::option::Option { return self.from_slot; } /// Validates all deterministic common subscribe-request bounds before any network I/O. pub fn validate(&self) -> ksp_core_lib::Result<()> { if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter-group count exceeds KSP bounds") .with_context("field", "grpc_subscribe.filter_groups"), ); } if self.accounts_data_slice.len() > MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice count exceeds KSP bounds") .with_context("field", "grpc_subscribe.accounts_data_slice"), ); } return std::result::Result::Ok(()); } fn to_wire(&self) -> ksp_core_lib::Result { let validation = self.validate(); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } return std::result::Result::Ok(yellowstone_grpc_proto::geyser::SubscribeRequest { accounts: self.accounts.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(), slots: self.slots.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(), transactions: self.transactions.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(), transactions_status: self.transactions_status.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(), blocks: self.blocks.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(), blocks_meta: self.blocks_meta.iter().map(|(name, filter)| return (name.as_str().to_owned(), (*filter).to_wire())).collect(), entry: self.entry.iter().map(|(name, filter)| return (name.as_str().to_owned(), (*filter).to_wire())).collect(), commitment: commitment_to_wire(self.commitment), accounts_data_slice: self.accounts_data_slice.iter().copied().map(crate::YellowstoneAccountsDataSlice::to_wire).collect(), ping: self.ping.map(crate::YellowstoneSubscribePing::to_wire), from_slot: self.from_slot, }); } fn total_filter_count(&self) -> usize { return self.accounts.len() + self.slots.len() + self.transactions.len() + self.transactions_status.len() + self.blocks.len() + self.blocks_meta.len() + self.entry.len(); } fn contains_filter_name(&self, name: &crate::YellowstoneSubscribeFilterName) -> bool { return self.accounts.contains_key(name) || self.slots.contains_key(name) || self.transactions.contains_key(name) || self.transactions_status.contains_key(name) || self.blocks.contains_key(name) || self.blocks_meta.contains_key(name) || self.entry.contains_key(name); } fn validate_new_filter_name(&self, name: &crate::YellowstoneSubscribeFilterName) -> ksp_core_lib::Result<()> { if self.contains_filter_name(name) { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter name must be globally unique") .with_context("field", "grpc_subscribe.filter_name"), ); } if self.total_filter_count() >= MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter-group count exceeds KSP bounds") .with_context("field", "grpc_subscribe.filter_groups"), ); } return std::result::Result::Ok(()); } } fn decode_account_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(value)) => value, _ => return invalid_subscribe_response("account", "Yellowstone update does not contain an account payload"), }; let account = match update.account { std::option::Option::Some(value) => match decode_account_info(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => return invalid_subscribe_response("account", "Yellowstone account update is missing account info"), }; return std::result::Result::Ok(crate::YellowstoneAccountUpdate { filters, created_at, account, slot: update.slot, is_startup: update.is_startup }); } fn decode_slot_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(value)) => value, _ => return invalid_subscribe_response("slot", "Yellowstone update does not contain a slot payload"), }; let status = match update.status { value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotProcessed as i32 => crate::YellowstoneSlotStatus::Processed, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotConfirmed as i32 => crate::YellowstoneSlotStatus::Confirmed, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotFinalized as i32 => crate::YellowstoneSlotStatus::Finalized, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotFirstShredReceived as i32 => crate::YellowstoneSlotStatus::FirstShredReceived, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotCompleted as i32 => crate::YellowstoneSlotStatus::Completed, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotCreatedBank as i32 => crate::YellowstoneSlotStatus::CreatedBank, value if value == yellowstone_grpc_proto::geyser::SlotStatus::SlotDead as i32 => crate::YellowstoneSlotStatus::Dead, _ => return invalid_subscribe_response("slot.status", "Yellowstone slot update contains an unknown status"), }; if update.dead_error.as_ref().is_some_and(|value| return value.len() > MAX_GRPC_SUBSCRIBE_SLOT_DEAD_ERROR_LENGTH_BYTES) { return invalid_subscribe_response("slot.dead_error", "Yellowstone dead-slot diagnostic exceeds the KSP bound"); } return std::result::Result::Ok(crate::YellowstoneSlotUpdate { filters, created_at, slot: update.slot, parent: update.parent, status, dead_error: update.dead_error, }); } fn decode_transaction_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Transaction(value)) => value, _ => return invalid_subscribe_response("transaction", "Yellowstone update does not contain a transaction payload"), }; let transaction = match update.transaction { std::option::Option::Some(value) => match decode_transaction_info(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => return invalid_subscribe_response("transaction", "Yellowstone transaction update is missing transaction info"), }; return std::result::Result::Ok(crate::YellowstoneTransactionUpdate { filters, created_at, transaction, slot: update.slot }); } fn decode_transaction_status_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::TransactionStatus(value)) => value, _ => return invalid_subscribe_response("transaction_status", "Yellowstone update does not contain a transaction-status payload"), }; let signature = match decode_transaction_signature("transaction_status.signature", update.signature) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let error = match update.err { std::option::Option::Some(value) => match decode_transaction_error("transaction_status.err", value) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::YellowstoneTransactionStatusUpdate { filters, created_at, slot: update.slot, signature, is_vote: update.is_vote, index: update.index, error, }); } fn decode_ping_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Ping(_)) => {}, _ => return invalid_subscribe_response("ping", "Yellowstone update does not contain a ping payload"), } return std::result::Result::Ok(crate::YellowstoneSubscribePingUpdate { filters, created_at }); } fn decode_pong_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let pong = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Pong(value)) => value, _ => return invalid_subscribe_response("pong", "Yellowstone update does not contain a pong payload"), }; return std::result::Result::Ok(crate::YellowstoneSubscribePongUpdate { filters, created_at, id: pong.id }); } fn decode_block_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(value)) => value, _ => return invalid_subscribe_response("block", "Yellowstone update does not contain a block payload"), }; if update.transactions.len() > MAX_GRPC_BLOCK_VECTOR_COUNT || update.accounts.len() > MAX_GRPC_BLOCK_VECTOR_COUNT || update.entries.len() > MAX_GRPC_BLOCK_VECTOR_COUNT { return invalid_subscribe_response("block", "Yellowstone block payload collection exceeds the KSP bound"); } let blockhash = match decode_blockhash_text("block.blockhash", update.blockhash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let parent_blockhash = match decode_blockhash_text("block.parent_blockhash", update.parent_blockhash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let rewards = match update.rewards { std::option::Option::Some(value) => match decode_block_rewards(value) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => std::option::Option::None, }; let block_time = update.block_time.map(|value| return value.timestamp); let block_height = update.block_height.map(|value| return value.block_height); let mut transactions = std::vec::Vec::with_capacity(update.transactions.len()); for value in update.transactions { let value = match decode_transaction_info(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; transactions.push(value); } let mut accounts = std::vec::Vec::with_capacity(update.accounts.len()); for value in update.accounts { let value = match decode_account_info(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; accounts.push(value); } let mut entries = std::vec::Vec::with_capacity(update.entries.len()); for value in update.entries { let value = match decode_entry_info(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; entries.push(value); } return std::result::Result::Ok(crate::YellowstoneBlockUpdate { filters, created_at, slot: update.slot, blockhash, rewards, block_time, block_height, parent_slot: update.parent_slot, parent_blockhash, executed_transaction_count: update.executed_transaction_count, transactions, updated_account_count: update.updated_account_count, accounts, entries_count: update.entries_count, entries, }); } fn decode_block_meta_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::BlockMeta(value)) => value, _ => return invalid_subscribe_response("block_meta", "Yellowstone update does not contain a block-meta payload"), }; let blockhash = match decode_blockhash_text("block_meta.blockhash", update.blockhash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let parent_blockhash = match decode_blockhash_text("block_meta.parent_blockhash", update.parent_blockhash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let rewards = match update.rewards { std::option::Option::Some(value) => match decode_block_rewards(value) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::YellowstoneBlockMetaUpdate { filters, created_at, slot: update.slot, blockhash, rewards, block_time: update.block_time.map(|value| return value.timestamp), block_height: update.block_height.map(|value| return value.block_height), parent_slot: update.parent_slot, parent_blockhash, executed_transaction_count: update.executed_transaction_count, entries_count: update.entries_count, }); } fn decode_entry_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result { let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let update = match wire.update_oneof { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(value)) => value, _ => return invalid_subscribe_response("entry", "Yellowstone update does not contain an entry payload"), }; let entry = match decode_entry_info(update) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::YellowstoneEntryUpdate { filters, created_at, entry }); } fn decode_block_rewards(wire: yellowstone_grpc_proto::solana::storage::confirmed_block::Rewards) -> ksp_core_lib::Result { if wire.rewards.len() > MAX_GRPC_BLOCK_VECTOR_COUNT { return invalid_subscribe_response("block.rewards", "Yellowstone block reward count exceeds the KSP bound"); } let mut rewards = std::vec::Vec::with_capacity(wire.rewards.len()); for value in wire.rewards { let value = match decode_reward(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; rewards.push(value); } return std::result::Result::Ok(crate::YellowstoneBlockRewards { rewards, num_partitions: wire.num_partitions.map(|value| return value.num_partitions) }); } fn decode_entry_info(wire: yellowstone_grpc_proto::geyser::SubscribeUpdateEntry) -> ksp_core_lib::Result { let hash = match decode_entry_hash(wire.hash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::YellowstoneEntryInfo { slot: wire.slot, index: wire.index, num_hashes: wire.num_hashes, hash, executed_transaction_count: wire.executed_transaction_count, starting_transaction_index: wire.starting_transaction_index, }); } fn decode_entry_hash(bytes: std::vec::Vec) -> ksp_core_lib::Result { let bytes: [u8; YELLOWSTONE_HASH_LENGTH_BYTES] = match bytes.try_into() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response("entry.hash", "Yellowstone entry contains an invalid hash length"), }; return std::result::Result::Ok(crate::YellowstoneHashBytes { bytes }); } fn decode_blockhash_text(field: &'static str, value: std::string::String) -> ksp_core_lib::Result { if value.is_empty() || value.len() > MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES || value.trim() != value || base58_decoded_length(value.as_str()) != std::option::Option::Some(YELLOWSTONE_HASH_LENGTH_BYTES) { return invalid_subscribe_response(field, "Yellowstone block update contains an invalid Solana blockhash"); } return std::result::Result::Ok(value); } fn decode_transaction_info(wire: yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo) -> ksp_core_lib::Result { let signature = match decode_transaction_signature("transaction.signature", wire.signature) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let transaction = match wire.transaction { std::option::Option::Some(value) => match decode_stored_transaction(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => return invalid_subscribe_response("transaction.transaction", "Yellowstone transaction info is missing transaction body"), }; let meta = match wire.meta { std::option::Option::Some(value) => match decode_transaction_status_meta(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => return invalid_subscribe_response("transaction.meta", "Yellowstone transaction info is missing status metadata"), }; return std::result::Result::Ok(crate::YellowstoneTransactionInfo { signature, is_vote: wire.is_vote, transaction, meta, index: wire.index }); } fn decode_stored_transaction( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::Transaction, ) -> ksp_core_lib::Result { if wire.signatures.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT { return invalid_subscribe_response("transaction.signatures", "Yellowstone transaction signature count exceeds the KSP bound"); } let mut signatures = std::vec::Vec::with_capacity(wire.signatures.len()); for signature in wire.signatures { let signature = match decode_transaction_signature("transaction.signatures", signature) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; signatures.push(signature); } let message = match wire.message { std::option::Option::Some(value) => match decode_transaction_message(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => return invalid_subscribe_response("transaction.message", "Yellowstone transaction body is missing message"), }; return std::result::Result::Ok(crate::YellowstoneStoredTransaction { signatures, message }); } fn decode_transaction_message( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::Message, ) -> ksp_core_lib::Result { if wire.account_keys.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.instructions.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.address_table_lookups.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT { return invalid_subscribe_response("transaction.message", "Yellowstone transaction message collection exceeds the KSP bound"); } let header = match wire.header { std::option::Option::Some(value) => crate::YellowstoneTransactionMessageHeader { num_required_signatures: value.num_required_signatures, num_readonly_signed_accounts: value.num_readonly_signed_accounts, num_readonly_unsigned_accounts: value.num_readonly_unsigned_accounts, }, std::option::Option::None => return invalid_subscribe_response("transaction.message.header", "Yellowstone transaction message is missing header"), }; let mut account_keys = std::vec::Vec::with_capacity(wire.account_keys.len()); for value in wire.account_keys { let value = match decode_pubkey_bytes("transaction.message.account_keys", value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; account_keys.push(value); } let recent_blockhash = match decode_hash_bytes("transaction.message.recent_blockhash", wire.recent_blockhash) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut instructions = std::vec::Vec::with_capacity(wire.instructions.len()); for value in wire.instructions { let value = match decode_compiled_instruction(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; instructions.push(value); } let mut address_table_lookups = std::vec::Vec::with_capacity(wire.address_table_lookups.len()); for value in wire.address_table_lookups { let value = match decode_message_address_table_lookup(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; address_table_lookups.push(value); } let config = wire.config.map(|value| { return crate::YellowstoneTransactionConfig { priority_fee: value.priority_fee, compute_unit_limit: value.compute_unit_limit, loaded_accounts_data_size_limit: value.loaded_accounts_data_size_limit, heap_size: value.heap_size, }; }); return std::result::Result::Ok(crate::YellowstoneTransactionMessage { header, account_keys, recent_blockhash, instructions, versioned: wire.versioned, address_table_lookups, config, }); } fn decode_compiled_instruction( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::CompiledInstruction, ) -> ksp_core_lib::Result { if wire.accounts.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.data.len() > MAX_GRPC_TRANSACTION_INSTRUCTION_DATA_BYTES { return invalid_subscribe_response("transaction.message.instructions", "Yellowstone compiled instruction exceeds the KSP bound"); } return std::result::Result::Ok(crate::YellowstoneCompiledInstruction { program_id_index: wire.program_id_index, accounts: wire.accounts, data: wire.data, }); } fn decode_message_address_table_lookup( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::MessageAddressTableLookup, ) -> ksp_core_lib::Result { if wire.writable_indexes.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.readonly_indexes.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT { return invalid_subscribe_response("transaction.message.address_table_lookups", "Yellowstone address-table index collection exceeds the KSP bound"); } let account_key = match decode_pubkey_bytes("transaction.message.address_table_lookup.account_key", wire.account_key) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::YellowstoneMessageAddressTableLookup { account_key, writable_indexes: wire.writable_indexes, readonly_indexes: wire.readonly_indexes, }); } fn decode_transaction_status_meta( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionStatusMeta, ) -> ksp_core_lib::Result { if wire.pre_balances.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.post_balances.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.inner_instructions.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.log_messages.len() > MAX_GRPC_TRANSACTION_LOG_COUNT || wire.pre_token_balances.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.post_token_balances.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.rewards.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.loaded_writable_addresses.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || wire.loaded_readonly_addresses.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT { return invalid_subscribe_response("transaction.meta", "Yellowstone transaction metadata collection exceeds the KSP bound"); } let error = match wire.err { std::option::Option::Some(value) => match decode_transaction_error("transaction.meta.err", value) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => std::option::Option::None, }; let mut inner_instructions = std::vec::Vec::with_capacity(wire.inner_instructions.len()); for value in wire.inner_instructions { let value = match decode_inner_instructions(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; inner_instructions.push(value); } for value in &wire.log_messages { if value.len() > MAX_GRPC_TRANSACTION_LOG_LENGTH_BYTES { return invalid_subscribe_response("transaction.meta.log_messages", "Yellowstone transaction log message exceeds the KSP bound"); } } let mut pre_token_balances = std::vec::Vec::with_capacity(wire.pre_token_balances.len()); for value in wire.pre_token_balances { let value = match decode_token_balance(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; pre_token_balances.push(value); } let mut post_token_balances = std::vec::Vec::with_capacity(wire.post_token_balances.len()); for value in wire.post_token_balances { let value = match decode_token_balance(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; post_token_balances.push(value); } let mut rewards = std::vec::Vec::with_capacity(wire.rewards.len()); for value in wire.rewards { let value = match decode_reward(value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; rewards.push(value); } let mut loaded_writable_addresses = std::vec::Vec::with_capacity(wire.loaded_writable_addresses.len()); for value in wire.loaded_writable_addresses { let value = match decode_pubkey_bytes("transaction.meta.loaded_writable_addresses", value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; loaded_writable_addresses.push(value); } let mut loaded_readonly_addresses = std::vec::Vec::with_capacity(wire.loaded_readonly_addresses.len()); for value in wire.loaded_readonly_addresses { let value = match decode_pubkey_bytes("transaction.meta.loaded_readonly_addresses", value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; loaded_readonly_addresses.push(value); } let return_data = match wire.return_data { std::option::Option::Some(value) => match decode_return_data(value) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::YellowstoneTransactionStatusMeta { error, fee: wire.fee, pre_balances: wire.pre_balances, post_balances: wire.post_balances, inner_instructions, inner_instructions_none: wire.inner_instructions_none, log_messages: wire.log_messages, log_messages_none: wire.log_messages_none, pre_token_balances, post_token_balances, rewards, loaded_writable_addresses, loaded_readonly_addresses, return_data, return_data_none: wire.return_data_none, compute_units_consumed: wire.compute_units_consumed, cost_units: wire.cost_units, }); } fn decode_inner_instructions( wire: yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstructions, ) -> ksp_core_lib::Result { if wire.instructions.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT { return invalid_subscribe_response("transaction.meta.inner_instructions", "Yellowstone inner-instruction group exceeds the KSP bound"); } let mut instructions = std::vec::Vec::with_capacity(wire.instructions.len()); for value in wire.instructions { if value.accounts.len() > MAX_GRPC_TRANSACTION_VECTOR_COUNT || value.data.len() > MAX_GRPC_TRANSACTION_INSTRUCTION_DATA_BYTES { return invalid_subscribe_response("transaction.meta.inner_instructions", "Yellowstone inner instruction exceeds the KSP bound"); } instructions.push(crate::YellowstoneInnerInstruction { program_id_index: value.program_id_index, accounts: value.accounts, data: value.data, stack_height: value.stack_height, }); } return std::result::Result::Ok(crate::YellowstoneInnerInstructions { index: wire.index, instructions }); } fn decode_token_balance(wire: yellowstone_grpc_proto::solana::storage::confirmed_block::TokenBalance) -> ksp_core_lib::Result { if wire.mint.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES || wire.owner.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES || wire.program_id.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES { return invalid_subscribe_response("transaction.meta.token_balances", "Yellowstone token-balance text exceeds the KSP bound"); } let ui_token_amount = match wire.ui_token_amount { std::option::Option::Some(value) => { if value.amount.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES || value.ui_amount_string.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES { return invalid_subscribe_response("transaction.meta.token_balances", "Yellowstone token amount text exceeds the KSP bound"); } std::option::Option::Some(crate::YellowstoneUiTokenAmount { ui_amount: value.ui_amount, decimals: value.decimals, amount: value.amount, ui_amount_string: value.ui_amount_string, }) }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::YellowstoneTokenBalance { account_index: wire.account_index, mint: wire.mint, ui_token_amount, owner: wire.owner, program_id: wire.program_id, }); } fn decode_return_data(wire: yellowstone_grpc_proto::solana::storage::confirmed_block::ReturnData) -> ksp_core_lib::Result { if wire.data.len() > MAX_GRPC_TRANSACTION_RETURN_DATA_BYTES { return invalid_subscribe_response("transaction.meta.return_data", "Yellowstone transaction return data exceeds the KSP bound"); } let program_id = match decode_pubkey_bytes("transaction.meta.return_data.program_id", wire.program_id) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::YellowstoneReturnData { program_id, data: wire.data }); } fn decode_reward(wire: yellowstone_grpc_proto::solana::storage::confirmed_block::Reward) -> ksp_core_lib::Result { if wire.pubkey.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES || wire.commission.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES || wire.commission_bps.len() > MAX_GRPC_TRANSACTION_TEXT_LENGTH_BYTES { return invalid_subscribe_response("transaction.meta.rewards", "Yellowstone reward text exceeds the KSP bound"); } let pubkey = match wire.pubkey.parse::() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return invalid_subscribe_response("transaction.meta.rewards.pubkey", "Yellowstone reward contains an invalid public key"); }, }; let reward_type = match wire.reward_type { value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Unspecified as i32 => crate::YellowstoneRewardType::Unspecified, value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Fee as i32 => crate::YellowstoneRewardType::Fee, value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Rent as i32 => crate::YellowstoneRewardType::Rent, value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Staking as i32 => crate::YellowstoneRewardType::Staking, value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Voting as i32 => crate::YellowstoneRewardType::Voting, value if value == yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::DeactivatedStake as i32 => { crate::YellowstoneRewardType::DeactivatedStake }, value => crate::YellowstoneRewardType::Unknown(value), }; return std::result::Result::Ok(crate::YellowstoneReward { pubkey, lamports: wire.lamports, post_balance: wire.post_balance, reward_type, commission: wire.commission, commission_bps: wire.commission_bps, }); } fn decode_transaction_error( field: &'static str, wire: yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionError, ) -> ksp_core_lib::Result { if wire.err.len() > MAX_GRPC_TRANSACTION_ERROR_BYTES { return invalid_subscribe_response(field, "Yellowstone transaction error bytes exceed the KSP bound"); } return std::result::Result::Ok(crate::YellowstoneTransactionError { bytes: wire.err }); } fn decode_transaction_signature(field: &'static str, bytes: std::vec::Vec) -> ksp_core_lib::Result { let bytes: [u8; YELLOWSTONE_TRANSACTION_SIGNATURE_WIRE_LENGTH_BYTES] = match bytes.try_into() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response(field, "Yellowstone transaction contains an invalid signature length"), }; return std::result::Result::Ok(crate::YellowstoneTransactionSignature::new(bytes)); } fn decode_hash_bytes(field: &'static str, bytes: std::vec::Vec) -> ksp_core_lib::Result { let bytes: [u8; YELLOWSTONE_HASH_LENGTH_BYTES] = match bytes.try_into() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response(field, "Yellowstone transaction contains an invalid hash length"), }; return std::result::Result::Ok(crate::YellowstoneHashBytes { bytes }); } fn decode_update_envelope( filters: std::vec::Vec, created_at: std::option::Option, ) -> ksp_core_lib::Result<(std::vec::Vec, std::option::Option)> { if filters.len() > MAX_GRPC_SUBSCRIBE_UPDATE_FILTER_COUNT { return invalid_subscribe_response("filters", "Yellowstone update filter-name count exceeds the KSP bound"); } let mut decoded_filters = std::vec::Vec::with_capacity(filters.len()); for filter in filters { let decoded = match crate::YellowstoneSubscribeFilterName::new(filter) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response("filters", "Yellowstone update contains an invalid filter name"), }; decoded_filters.push(decoded); } let decoded_created_at = match created_at { std::option::Option::Some(value) => { let nanos = match u32::try_from(value.nanos) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response("created_at", "Yellowstone update timestamp contains negative nanoseconds"), }; match crate::YellowstoneUpdateTimestamp::new(value.seconds, nanos) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), } }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok((decoded_filters, decoded_created_at)); } fn decode_account_info(wire: yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo) -> ksp_core_lib::Result { if wire.data.len() > MAX_GRPC_SUBSCRIBE_ACCOUNT_DATA_LENGTH_BYTES { return invalid_subscribe_response("account.data", "Yellowstone account data exceeds the KSP bound"); } let pubkey = match decode_pubkey_bytes("account.pubkey", wire.pubkey) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let owner = match decode_pubkey_bytes("account.owner", wire.owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let transaction_signature = match wire.txn_signature { std::option::Option::Some(value) => { let bytes: [u8; YELLOWSTONE_TRANSACTION_SIGNATURE_WIRE_LENGTH_BYTES] = match value.try_into() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return invalid_subscribe_response("account.txn_signature", "Yellowstone account update contains an invalid transaction signature length"); }, }; std::option::Option::Some(crate::YellowstoneTransactionSignature::new(bytes)) }, std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::YellowstoneAccountInfo { pubkey, lamports: wire.lamports, owner, executable: wire.executable, rent_epoch: wire.rent_epoch, data: wire.data, write_version: wire.write_version, transaction_signature, }); } fn decode_pubkey_bytes(field: &'static str, bytes: std::vec::Vec) -> ksp_core_lib::Result { let array: [u8; 32] = match bytes.try_into() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return invalid_subscribe_response(field, "Yellowstone account update contains an invalid public-key length"), }; return std::result::Result::Ok(ksp_core_lib::Pubkey::new_from_array(array)); } fn push_block_selector(target: &mut std::vec::Vec, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> { if target.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone block account selector count exceeds KSP bounds") .with_context("field", "grpc_subscribe.block.account_include"), ); } target.push(value); return std::result::Result::Ok(()); } fn push_transaction_selector(target: &mut std::vec::Vec, value: ksp_core_lib::Pubkey, field: &'static str) -> ksp_core_lib::Result<()> { if target.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone transaction account selector count exceeds KSP bounds") .with_context("field", field), ); } target.push(value); return std::result::Result::Ok(()); } fn base58_decoded_length(value: &str) -> std::option::Option { let leading_zero_bytes = value.bytes().take_while(|value| return *value == b'1').count(); let mut decoded = std::vec::Vec::::new(); for value in value.bytes() { let digit = match base58_digit(value) { std::option::Option::Some(value) => u32::from(value), std::option::Option::None => return std::option::Option::None, }; let mut carry = digit; for byte in &mut decoded { let expanded = u32::from(*byte) * 58 + carry; let low = match u8::try_from(expanded & 0xff) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; *byte = low; carry = expanded >> 8; } while carry > 0 { let low = match u8::try_from(carry & 0xff) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; decoded.push(low); carry >>= 8; } } return leading_zero_bytes.checked_add(decoded.len()); } const fn base58_digit(value: u8) -> std::option::Option { return match value { b'1'..=b'9' => std::option::Option::Some(value - b'1'), b'A'..=b'H' => std::option::Option::Some(value - b'A' + 9), b'J'..=b'N' => std::option::Option::Some(value - b'J' + 17), b'P'..=b'Z' => std::option::Option::Some(value - b'P' + 22), b'a'..=b'k' => std::option::Option::Some(value - b'a' + 33), b'm'..=b'z' => std::option::Option::Some(value - b'm' + 44), _ => std::option::Option::None, }; } fn validate_memcmp_text(value: &str) -> ksp_core_lib::Result<()> { if value.len() > MAX_GRPC_SUBSCRIBE_MEMCMP_TEXT_LENGTH_BYTES || !value.is_ascii() || value.chars().any(char::is_whitespace) { return invalid_subscribe_parameter("grpc_subscribe.accounts.filters.memcmp.text", "Yellowstone memcmp text violates KSP bounds"); } return std::result::Result::Ok(()); } /// Converts one validated KSP subscribe request to the internal Yellowstone protobuf wire. pub(crate) fn yellowstone_subscribe_request_to_wire( request: &crate::YellowstoneSubscribeRequest, ) -> ksp_core_lib::Result { return request.to_wire(); } /// Decodes one internal Yellowstone protobuf update into the complete KSP-owned update enum. pub(crate) fn yellowstone_subscribe_update_from_wire( wire: yellowstone_grpc_proto::geyser::SubscribeUpdate, ) -> ksp_core_lib::Result { let kind = match wire.update_oneof.as_ref() { std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(_)) => 1_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(_)) => 2_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Transaction(_)) => 3_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::TransactionStatus(_)) => 4_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(_)) => 5_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Ping(_)) => 6_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Pong(_)) => 7_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::BlockMeta(_)) => 8_u8, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(_)) => 9_u8, std::option::Option::None => { return invalid_subscribe_response("update.oneof", "Yellowstone subscribe update is missing its oneof payload"); }, }; return match kind { 1 => decode_account_update(wire).map(crate::YellowstoneSubscribeUpdate::Account), 2 => decode_slot_update(wire).map(crate::YellowstoneSubscribeUpdate::Slot), 3 => decode_transaction_update(wire).map(|update| { return crate::YellowstoneSubscribeUpdate::Transaction(std::boxed::Box::new(update)); }), 4 => decode_transaction_status_update(wire).map(crate::YellowstoneSubscribeUpdate::TransactionStatus), 5 => decode_block_update(wire).map(crate::YellowstoneSubscribeUpdate::Block), 6 => decode_ping_update(wire).map(crate::YellowstoneSubscribeUpdate::Ping), 7 => decode_pong_update(wire).map(crate::YellowstoneSubscribeUpdate::Pong), 8 => decode_block_meta_update(wire).map(crate::YellowstoneSubscribeUpdate::BlockMeta), 9 => decode_entry_update(wire).map(crate::YellowstoneSubscribeUpdate::Entry), _ => invalid_subscribe_response("update.oneof", "Yellowstone subscribe update kind is unsupported"), }; } fn invalid_subscribe_parameter(field: &'static str, message: &'static str) -> ksp_core_lib::Result { return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, message).with_context("field", field)); } fn invalid_subscribe_response(field: &'static str, message: &'static str) -> ksp_core_lib::Result { return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, message).with_context("grpc_update", field)); } fn commitment_to_wire(commitment: std::option::Option) -> std::option::Option { return commitment.map(|value| { return match value { crate::SolanaCommitment::Processed => yellowstone_grpc_proto::geyser::CommitmentLevel::Processed as i32, crate::SolanaCommitment::Confirmed => yellowstone_grpc_proto::geyser::CommitmentLevel::Confirmed as i32, crate::SolanaCommitment::Finalized => yellowstone_grpc_proto::geyser::CommitmentLevel::Finalized as i32, }; }); } impl std::default::Default for YellowstoneSubscribeRequest { fn default() -> Self { return Self::new(); } } impl std::fmt::Debug for YellowstoneSubscribeRequest { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("YellowstoneSubscribeRequest") .field("account_filter_count", &self.accounts.len()) .field("slot_filter_count", &self.slots.len()) .field("transaction_filter_count", &self.transactions.len()) .field("transaction_status_filter_count", &self.transactions_status.len()) .field("block_filter_count", &self.blocks.len()) .field("blocks_meta_filter_count", &self.blocks_meta.len()) .field("entry_filter_count", &self.entry.len()) .field("commitment", &self.commitment) .field("accounts_data_slice_count", &self.accounts_data_slice.len()) .field("ping", &self.ping) .field("from_slot", &self.from_slot) .finish(); } } #[cfg(test)] #[path = "../unit_tests/grpc_subscribe.rs"] mod tests;