Files
khadhroony-solana-project/crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
2026-09-10 09:50:42 +02:00

3821 lines
154 KiB
Rust

// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
// version: 9
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<std::string::String>) -> ksp_core_lib::Result<Self> {
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(<redacted>)");
}
}
/// 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<Self> {
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<u8>,
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<u8>,
bucket_count: u32,
entries_per_bucket: u32,
fingerprint_bits: u32,
hash_seed: u64,
hash_algorithm: crate::YellowstoneCuckooHashAlgorithm,
) -> ksp_core_lib::Result<Self> {
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<u8>),
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<u8>) -> ksp_core_lib::Result<Self> {
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<std::string::String>) -> ksp_core_lib::Result<Self> {
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<std::string::String>) -> ksp_core_lib::Result<Self> {
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<ksp_core_lib::Pubkey>,
owners: std::vec::Vec<ksp_core_lib::Pubkey>,
filters: std::vec::Vec<crate::YellowstoneAccountFilterPredicate>,
nonempty_txn_signature: std::option::Option<bool>,
cuckoo_accounts_filter: std::option::Option<crate::YellowstoneCuckooFilter>,
}
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<bool>) {
self.nonempty_txn_signature = value;
}
/// Sets or clears the compressed account selector.
pub fn set_cuckoo_accounts_filter(&mut self, value: std::option::Option<crate::YellowstoneCuckooFilter>) {
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<bool> {
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<bool>,
interslot_updates: std::option::Option<bool>,
}
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<bool>) {
self.filter_by_commitment = value;
}
/// Sets or clears interslot status updates.
pub fn set_interslot_updates(&mut self, value: std::option::Option<bool>) {
self.interslot_updates = value;
}
/// Returns the optional commitment-filtering flag.
#[must_use]
pub const fn filter_by_commitment(self) -> std::option::Option<bool> {
return self.filter_by_commitment;
}
/// Returns the optional interslot-update flag.
#[must_use]
pub const fn interslot_updates(self) -> std::option::Option<bool> {
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<Self> {
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(<redacted>)");
}
}
/// 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<u8>,
write_version: u64,
transaction_signature: std::option::Option<crate::YellowstoneTransactionSignature>,
}
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<crate::YellowstoneTransactionSignature> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
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<crate::YellowstoneUpdateTimestamp> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
slot: u64,
parent: std::option::Option<u64>,
status: crate::YellowstoneSlotStatus,
dead_error: std::option::Option<std::string::String>,
}
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<crate::YellowstoneUpdateTimestamp> {
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<u64> {
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<std::string::String>) -> ksp_core_lib::Result<Self> {
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(<redacted>)");
}
}
/// Complete current transaction-family filter shared by `transactions` and `transactions_status`.
#[derive(Clone, Eq, PartialEq)]
pub struct YellowstoneSubscribeTransactionFilter {
vote: std::option::Option<bool>,
failed: std::option::Option<bool>,
signature: std::option::Option<crate::YellowstoneTransactionSignatureSelector>,
account_include: std::vec::Vec<ksp_core_lib::Pubkey>,
account_exclude: std::vec::Vec<ksp_core_lib::Pubkey>,
account_required: std::vec::Vec<ksp_core_lib::Pubkey>,
cuckoo_account_include: std::option::Option<crate::YellowstoneCuckooFilter>,
token_accounts: std::option::Option<crate::YellowstoneTokenAccountExpansion>,
}
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<bool>) {
self.vote = value;
}
/// Sets or clears the optional failed-transaction selector.
pub fn set_failed(&mut self, value: std::option::Option<bool>) {
self.failed = value;
}
/// Sets or clears the exact transaction-signature selector.
pub fn set_signature(&mut self, value: std::option::Option<crate::YellowstoneTransactionSignatureSelector>) {
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<crate::YellowstoneCuckooFilter>) {
self.cuckoo_account_include = value;
}
/// Sets or clears token-account owner expansion.
pub fn set_token_accounts(&mut self, value: std::option::Option<crate::YellowstoneTokenAccountExpansion>) {
self.token_accounts = value;
}
/// Returns the optional vote selector.
#[must_use]
pub const fn vote(&self) -> std::option::Option<bool> {
return self.vote;
}
/// Returns the optional failed selector.
#[must_use]
pub const fn failed(&self) -> std::option::Option<bool> {
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<crate::YellowstoneTokenAccountExpansion> {
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(<redacted>)");
}
}
/// Opaque runtime transaction error bytes carried by `solana-storage.proto`.
#[derive(Clone, Eq, PartialEq)]
pub struct YellowstoneTransactionError {
bytes: std::vec::Vec<u8>,
}
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<u8>,
data: std::vec::Vec<u8>,
}
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<u8>,
readonly_indexes: std::vec::Vec<u8>,
}
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<u64>,
compute_unit_limit: std::option::Option<u32>,
loaded_accounts_data_size_limit: std::option::Option<u32>,
heap_size: std::option::Option<u32>,
}
impl YellowstoneTransactionConfig {
/// Returns the optional priority fee.
#[must_use]
pub const fn priority_fee(self) -> std::option::Option<u64> {
return self.priority_fee;
}
/// Returns the optional compute-unit limit.
#[must_use]
pub const fn compute_unit_limit(self) -> std::option::Option<u32> {
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<u32> {
return self.loaded_accounts_data_size_limit;
}
/// Returns the optional heap-size override.
#[must_use]
pub const fn heap_size(self) -> std::option::Option<u32> {
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<ksp_core_lib::Pubkey>,
recent_blockhash: crate::YellowstoneHashBytes,
instructions: std::vec::Vec<crate::YellowstoneCompiledInstruction>,
versioned: bool,
address_table_lookups: std::vec::Vec<crate::YellowstoneMessageAddressTableLookup>,
config: std::option::Option<crate::YellowstoneTransactionConfig>,
}
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<crate::YellowstoneTransactionConfig> {
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<crate::YellowstoneTransactionSignature>,
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<u8>,
data: std::vec::Vec<u8>,
stack_height: std::option::Option<u32>,
}
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<u32> {
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<crate::YellowstoneInnerInstruction>,
}
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<crate::YellowstoneUiTokenAmount>,
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<u8>,
}
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<crate::YellowstoneTransactionError>,
fee: u64,
pre_balances: std::vec::Vec<u64>,
post_balances: std::vec::Vec<u64>,
inner_instructions: std::vec::Vec<crate::YellowstoneInnerInstructions>,
inner_instructions_none: bool,
log_messages: std::vec::Vec<std::string::String>,
log_messages_none: bool,
pre_token_balances: std::vec::Vec<crate::YellowstoneTokenBalance>,
post_token_balances: std::vec::Vec<crate::YellowstoneTokenBalance>,
rewards: std::vec::Vec<crate::YellowstoneReward>,
loaded_writable_addresses: std::vec::Vec<ksp_core_lib::Pubkey>,
loaded_readonly_addresses: std::vec::Vec<ksp_core_lib::Pubkey>,
return_data: std::option::Option<crate::YellowstoneReturnData>,
return_data_none: bool,
compute_units_consumed: std::option::Option<u64>,
cost_units: std::option::Option<u64>,
}
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<u64> {
return self.compute_units_consumed;
}
/// Returns optional total transaction cost units.
#[must_use]
pub const fn cost_units(&self) -> std::option::Option<u64> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
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<crate::YellowstoneUpdateTimestamp> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
slot: u64,
signature: crate::YellowstoneTransactionSignature,
is_vote: bool,
index: u64,
error: std::option::Option<crate::YellowstoneTransactionError>,
}
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<crate::YellowstoneUpdateTimestamp> {
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<ksp_core_lib::Pubkey>,
include_transactions: std::option::Option<bool>,
include_accounts: std::option::Option<bool>,
include_entries: std::option::Option<bool>,
cuckoo_account_include: std::option::Option<crate::YellowstoneCuckooFilter>,
}
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<bool>) {
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<bool>) {
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<bool>) {
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<crate::YellowstoneCuckooFilter>) {
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<bool> {
return self.include_transactions;
}
/// Returns the optional account-payload inclusion flag.
#[must_use]
pub const fn include_accounts(&self) -> std::option::Option<bool> {
return self.include_accounts;
}
/// Returns the optional entry-payload inclusion flag.
#[must_use]
pub const fn include_entries(&self) -> std::option::Option<bool> {
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<crate::YellowstoneReward>,
num_partitions: std::option::Option<u64>,
}
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<u64> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
slot: u64,
blockhash: std::string::String,
rewards: std::option::Option<crate::YellowstoneBlockRewards>,
block_time: std::option::Option<i64>,
block_height: std::option::Option<u64>,
parent_slot: u64,
parent_blockhash: std::string::String,
executed_transaction_count: u64,
transactions: std::vec::Vec<crate::YellowstoneTransactionInfo>,
updated_account_count: u64,
accounts: std::vec::Vec<crate::YellowstoneAccountInfo>,
entries_count: u64,
entries: std::vec::Vec<crate::YellowstoneEntryInfo>,
}
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<crate::YellowstoneUpdateTimestamp> {
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<i64> {
return self.block_time;
}
/// Returns optional block height.
#[must_use]
pub const fn block_height(&self) -> std::option::Option<u64> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
slot: u64,
blockhash: std::string::String,
rewards: std::option::Option<crate::YellowstoneBlockRewards>,
block_time: std::option::Option<i64>,
block_height: std::option::Option<u64>,
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<crate::YellowstoneUpdateTimestamp> {
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<i64> {
return self.block_time;
}
/// Returns optional block height.
#[must_use]
pub const fn block_height(&self) -> std::option::Option<u64> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
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<crate::YellowstoneUpdateTimestamp> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
}
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<crate::YellowstoneUpdateTimestamp> {
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<crate::YellowstoneSubscribeFilterName>,
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
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<crate::YellowstoneUpdateTimestamp> {
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<crate::YellowstoneTransactionUpdate>),
/// 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();
}
}
/// Opaque deterministic identity material for one complete Yellowstone Subscribe request.
///
/// The encoded bytes remain private. `Hash` feeds those canonical bytes to a caller-provided hasher while `Debug` exposes only their length.
#[derive(Clone, Eq, PartialEq)]
pub struct YellowstoneSubscribeRequestIdentity {
bytes: std::vec::Vec<u8>,
}
impl std::hash::Hash for crate::YellowstoneSubscribeRequestIdentity {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(&(self.bytes.len() as u64).to_be_bytes());
state.write(self.bytes.as_slice());
return;
}
}
impl std::fmt::Debug for crate::YellowstoneSubscribeRequestIdentity {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("YellowstoneSubscribeRequestIdentity").field("byte_len", &self.bytes.len()).finish();
}
}
/// 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<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
slots: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeSlotFilter>,
transactions: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeTransactionFilter>,
transactions_status: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeTransactionFilter>,
blocks: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeBlockFilter>,
blocks_meta: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeBlocksMetaFilter>,
entry: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeEntryFilter>,
commitment: std::option::Option<crate::SolanaCommitment>,
accounts_data_slice: std::vec::Vec<crate::YellowstoneAccountsDataSlice>,
ping: std::option::Option<crate::YellowstoneSubscribePing>,
from_slot: std::option::Option<u64>,
}
impl crate::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<crate::SolanaCommitment>) {
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<crate::YellowstoneSubscribePing>) {
self.ping = ping;
}
/// Sets or clears the optional replay starting slot.
pub fn set_from_slot(&mut self, from_slot: std::option::Option<u64>) {
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<crate::SolanaCommitment> {
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<crate::YellowstoneSubscribePing> {
return self.ping;
}
/// Returns the optional replay starting slot.
#[must_use]
pub const fn from_slot(&self) -> std::option::Option<u64> {
return self.from_slot;
}
/// Builds one opaque deterministic identity for the complete logical Subscribe request.
///
/// The identity preserves filter-family separation, globally sorted filter names, exact filter wire payloads and common request options. Its Debug surface
/// exposes only the encoded byte length. Callers may hash the opaque value but cannot recover the underlying identity bytes through this API.
pub fn identity(&self) -> ksp_core_lib::Result<crate::YellowstoneSubscribeRequestIdentity> {
let validation = self.validate();
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
let mut bytes = std::vec::Vec::new();
append_subscribe_identity_map(&mut bytes, b"accounts", &self.accounts, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"blocks", &self.blocks, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"blocks_meta", &self.blocks_meta, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"entry", &self.entry, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"slots", &self.slots, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"transactions", &self.transactions, |filter| return filter.to_wire());
append_subscribe_identity_map(&mut bytes, b"transactions_status", &self.transactions_status, |filter| return filter.to_wire());
let mut common = match self.to_wire() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
common.accounts.clear();
common.blocks.clear();
common.blocks_meta.clear();
common.entry.clear();
common.slots.clear();
common.transactions.clear();
common.transactions_status.clear();
let common = yellowstone_grpc_proto::prost::Message::encode_to_vec(&common);
append_subscribe_identity_component(&mut bytes, b"common");
append_subscribe_identity_component(&mut bytes, common.as_slice());
return std::result::Result::Ok(crate::YellowstoneSubscribeRequestIdentity { bytes });
}
/// 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<yellowstone_grpc_proto::geyser::SubscribeRequest> {
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<crate::YellowstoneAccountUpdate> {
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<crate::YellowstoneSlotUpdate> {
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<crate::YellowstoneTransactionUpdate> {
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<crate::YellowstoneTransactionStatusUpdate> {
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<crate::YellowstoneSubscribePingUpdate> {
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<crate::YellowstoneSubscribePongUpdate> {
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<crate::YellowstoneBlockUpdate> {
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<crate::YellowstoneBlockMetaUpdate> {
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<crate::YellowstoneEntryUpdate> {
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<crate::YellowstoneBlockRewards> {
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<crate::YellowstoneEntryInfo> {
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<u8>) -> ksp_core_lib::Result<crate::YellowstoneHashBytes> {
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<std::string::String> {
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<crate::YellowstoneTransactionInfo> {
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<crate::YellowstoneStoredTransaction> {
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<crate::YellowstoneTransactionMessage> {
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<crate::YellowstoneCompiledInstruction> {
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<crate::YellowstoneMessageAddressTableLookup> {
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<crate::YellowstoneTransactionStatusMeta> {
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<crate::YellowstoneInnerInstructions> {
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<crate::YellowstoneTokenBalance> {
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<crate::YellowstoneReturnData> {
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<crate::YellowstoneReward> {
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::<ksp_core_lib::Pubkey>() {
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<crate::YellowstoneTransactionError> {
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<u8>) -> ksp_core_lib::Result<crate::YellowstoneTransactionSignature> {
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<u8>) -> ksp_core_lib::Result<crate::YellowstoneHashBytes> {
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<std::string::String>,
created_at: std::option::Option<yellowstone_grpc_proto::prost_types::Timestamp>,
) -> ksp_core_lib::Result<(std::vec::Vec<crate::YellowstoneSubscribeFilterName>, std::option::Option<crate::YellowstoneUpdateTimestamp>)> {
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<crate::YellowstoneAccountInfo> {
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<u8>) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
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<ksp_core_lib::Pubkey>, 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<ksp_core_lib::Pubkey>, 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<usize> {
let leading_zero_bytes = value.bytes().take_while(|value| return *value == b'1').count();
let mut decoded = std::vec::Vec::<u8>::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<u8> {
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<yellowstone_grpc_proto::geyser::SubscribeRequest> {
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<crate::YellowstoneSubscribeUpdate> {
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<T>(field: &'static str, message: &'static str) -> ksp_core_lib::Result<T> {
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<T>(field: &'static str, message: &'static str) -> ksp_core_lib::Result<T> {
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<crate::SolanaCommitment>) -> std::option::Option<i32> {
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,
};
});
}
fn append_subscribe_identity_component(output: &mut std::vec::Vec<u8>, value: &[u8]) {
output.extend_from_slice(&(value.len() as u64).to_be_bytes());
output.extend_from_slice(value);
return;
}
fn append_subscribe_identity_map<V, W, F>(
output: &mut std::vec::Vec<u8>,
family: &[u8],
values: &std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, V>,
mut to_wire: F,
) where
W: yellowstone_grpc_proto::prost::Message,
F: FnMut(&V) -> W,
{
append_subscribe_identity_component(output, family);
output.extend_from_slice(&(values.len() as u64).to_be_bytes());
for (name, filter) in values {
append_subscribe_identity_component(output, name.as_str().as_bytes());
let wire = yellowstone_grpc_proto::prost::Message::encode_to_vec(&to_wire(filter));
append_subscribe_identity_component(output, wire.as_slice());
}
return;
}
impl std::default::Default for crate::YellowstoneSubscribeRequest {
fn default() -> Self {
return Self::new();
}
}
impl std::fmt::Debug for crate::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;