3821 lines
154 KiB
Rust
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;
|