v0.2.9-pre.005
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
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_UPDATE_FILTER_COUNT: usize = 1_024;
|
||||
const YELLOWSTONE_TRANSACTION_SIGNATURE_LENGTH_BYTES: usize = 64;
|
||||
|
||||
/// Validated logical filter name used by the standard Yellowstone `SubscribeRequest` maps.
|
||||
///
|
||||
@@ -103,45 +112,723 @@ impl YellowstoneSubscribePing {
|
||||
}
|
||||
}
|
||||
|
||||
/// Account-family filter-group shell for the standard Yellowstone subscribe surface.
|
||||
/// 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 {
|
||||
#[cfg(test)]
|
||||
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.
|
||||
///
|
||||
/// `0.2.9-pre.004` intentionally materializes only the top-level map contract. Account selectors and account-specific filter oneofs are added by `pre.005`.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
/// 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;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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 {
|
||||
#[cfg(test)]
|
||||
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 {
|
||||
_private: (),
|
||||
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 const fn new() -> Self {
|
||||
return Self { _private: () };
|
||||
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();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts {
|
||||
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts::default();
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Slot-family filter-group shell for the standard Yellowstone subscribe surface.
|
||||
///
|
||||
/// Slot optional flags and update decoding are added by `0.2.9-pre.005`.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
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 {
|
||||
_private: (),
|
||||
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 { _private: () };
|
||||
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;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots {
|
||||
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots::default();
|
||||
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, Debug, 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +1218,153 @@ impl YellowstoneSubscribeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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 });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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| 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,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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_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,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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 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(());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn commitment_to_wire(commitment: std::option::Option<crate::SolanaCommitment>) -> std::option::Option<i32> {
|
||||
return commitment.map(|value| {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 37
|
||||
// version: 38
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -134,9 +134,29 @@ pub use self::grpc_settings::YellowstoneGrpcReconnectSettings;
|
||||
pub use self::grpc_settings::YellowstoneGrpcSessionSettings;
|
||||
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
||||
pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
|
||||
/// One validated standard Yellowstone account predicate.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountFilterPredicate;
|
||||
/// Typed account payload carried by one standard Yellowstone account update.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountInfo;
|
||||
/// Lamport comparison used by standard Yellowstone account filters.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountLamportsFilter;
|
||||
/// Validated standard Yellowstone account memcmp predicate.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountMemcmp;
|
||||
/// Encoding selected by one Yellowstone account memcmp predicate.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountMemcmpEncoding;
|
||||
/// Standard Yellowstone account-update projection owned by KSP.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountUpdate;
|
||||
/// One standard Yellowstone account-data slice.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountsDataSlice;
|
||||
/// Account-family filter-group shell for standard Yellowstone Subscribe.
|
||||
/// Wire-preserving KSP representation of a standard Yellowstone Cuckoo filter.
|
||||
pub use self::grpc_subscribe::YellowstoneCuckooFilter;
|
||||
/// Hash algorithm carried by a standard Yellowstone Cuckoo filter.
|
||||
pub use self::grpc_subscribe::YellowstoneCuckooHashAlgorithm;
|
||||
/// Current standard Yellowstone slot status.
|
||||
pub use self::grpc_subscribe::YellowstoneSlotStatus;
|
||||
/// Standard Yellowstone slot-update projection owned by KSP.
|
||||
pub use self::grpc_subscribe::YellowstoneSlotUpdate;
|
||||
/// Complete account-family filter group for standard Yellowstone Subscribe.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeAccountFilter;
|
||||
/// Block-family filter-group shell for standard Yellowstone Subscribe.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeBlockFilter;
|
||||
@@ -150,10 +170,14 @@ pub use self::grpc_subscribe::YellowstoneSubscribeFilterName;
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribePing;
|
||||
/// Provider-neutral standard Yellowstone Subscribe request.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
|
||||
/// Slot-family filter-group shell for standard Yellowstone Subscribe.
|
||||
/// Complete slot-family filter group for standard Yellowstone Subscribe.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
|
||||
/// Transaction-family filter-group shell shared by transactions and transaction-status maps.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeTransactionFilter;
|
||||
/// Fixed-width transaction signature attached to Yellowstone account updates when available.
|
||||
pub use self::grpc_subscribe::YellowstoneTransactionSignature;
|
||||
/// Timestamp attached to standard Yellowstone update envelopes.
|
||||
pub use self::grpc_subscribe::YellowstoneUpdateTimestamp;
|
||||
/// Standard Solana Yellowstone unary facade over one KSP-owned physical gRPC channel.
|
||||
pub use self::grpc_unary::SolanaYellowstoneGrpcUnaryClient;
|
||||
/// Block height returned by the standard Yellowstone unary surface.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 42
|
||||
// version: 43
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -866,3 +866,33 @@ fn public_v0_2_9_pre_004_yellowstone_subscribe_common_contract_is_available_from
|
||||
assert_eq!(request.ping(), std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneSubscribePing::new(7)));
|
||||
assert_eq!(request.from_slot(), std::option::Option::Some(99));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_v0_2_9_pre_005_yellowstone_accounts_slots_contract_is_available_from_crate_root() {
|
||||
let account = ksp_core_lib::Pubkey::new_from_array([1_u8; 32]);
|
||||
let owner = ksp_core_lib::Pubkey::new_from_array([2_u8; 32]);
|
||||
let mut accounts = ksp_onchain_transport_lib::YellowstoneSubscribeAccountFilter::new();
|
||||
assert!(accounts.push_account(account).is_ok());
|
||||
assert!(accounts.push_owner(owner).is_ok());
|
||||
let memcmp = ksp_onchain_transport_lib::YellowstoneAccountMemcmp::bytes(0, vec![1_u8, 2, 3]).expect("public memcmp must validate");
|
||||
assert!(accounts.push_filter(ksp_onchain_transport_lib::YellowstoneAccountFilterPredicate::Memcmp(memcmp)).is_ok());
|
||||
accounts.set_nonempty_txn_signature(std::option::Option::Some(true));
|
||||
let cuckoo =
|
||||
ksp_onchain_transport_lib::YellowstoneCuckooFilter::new(vec![0_u8; 16], 4, 4, 8, 1, ksp_onchain_transport_lib::YellowstoneCuckooHashAlgorithm::SipHash)
|
||||
.expect("public cuckoo filter must validate");
|
||||
accounts.set_cuckoo_accounts_filter(std::option::Option::Some(cuckoo));
|
||||
let mut slots = ksp_onchain_transport_lib::YellowstoneSubscribeSlotFilter::new();
|
||||
slots.set_filter_by_commitment(std::option::Option::Some(true));
|
||||
slots.set_interslot_updates(std::option::Option::Some(true));
|
||||
let timestamp = ksp_onchain_transport_lib::YellowstoneUpdateTimestamp::new(1, 2).expect("public timestamp must validate");
|
||||
let signature = ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([3_u8; 64]);
|
||||
assert_eq!(timestamp.nanos(), 2);
|
||||
assert_eq!(signature.as_bytes(), &[3_u8; 64]);
|
||||
assert_eq!(slots.filter_by_commitment(), std::option::Option::Some(true));
|
||||
let _account_info = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneAccountInfo>();
|
||||
let _account_update = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneAccountUpdate>();
|
||||
let _slot_update = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSlotUpdate>();
|
||||
let _slot_status = ksp_onchain_transport_lib::YellowstoneSlotStatus::FirstShredReceived;
|
||||
let _lamports = ksp_onchain_transport_lib::YellowstoneAccountLamportsFilter::Gt(10);
|
||||
let _encoding = ksp_onchain_transport_lib::YellowstoneAccountMemcmpEncoding::Bytes;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 35
|
||||
// version: 36
|
||||
|
||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||
|
||||
@@ -1111,3 +1111,47 @@ fn release_v0_2_9_pre_004_materializes_only_standard_subscribe_common_contract()
|
||||
let _transaction = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter>();
|
||||
let _block = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeBlockFilter>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_9_pre_005_completes_standard_accounts_and_slots_without_advancing_transactions_or_blocks() {
|
||||
let source = include_str!("../src/grpc_subscribe.rs");
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
for required in [
|
||||
"account: self.accounts",
|
||||
"owner: self.owners",
|
||||
"filters: self.filters",
|
||||
"nonempty_txn_signature",
|
||||
"cuckoo_accounts_filter",
|
||||
"SubscribeRequestFilterAccountsFilterMemcmp",
|
||||
"DataSize",
|
||||
"TokenAccountState",
|
||||
"YellowstoneAccountLamportsFilter",
|
||||
"filter_by_commitment",
|
||||
"interslot_updates",
|
||||
"YellowstoneAccountUpdate",
|
||||
"YellowstoneSlotUpdate",
|
||||
"YellowstoneTransactionSignature",
|
||||
"SlotFirstShredReceived",
|
||||
"SlotCompleted",
|
||||
"SlotCreatedBank",
|
||||
"SlotDead",
|
||||
"dead_error",
|
||||
] {
|
||||
assert!(source.contains(required), "missing pre.005 Accounts/Slots contract token: {required}");
|
||||
}
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT"));
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_ACCOUNT_PREDICATE_COUNT"));
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_CUCKOO_DATA_LENGTH_BYTES"));
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_ACCOUNT_DATA_LENGTH_BYTES"));
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_SLOT_DEAD_ERROR_LENGTH_BYTES"));
|
||||
assert!(source.contains("#[cfg(test)]\nfn decode_account_update"));
|
||||
assert!(source.contains("#[cfg(test)]\nfn decode_slot_update"));
|
||||
assert!(!source.contains("YellowstoneTransactionUpdate"));
|
||||
assert!(!source.contains("YellowstoneBlockUpdate"));
|
||||
assert!(!source.contains("SubscribeDeshred"));
|
||||
assert!(!source.contains("PublicNode"));
|
||||
assert!(!crate_root.contains("pub use tonic"));
|
||||
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||
let _account = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneAccountUpdate>();
|
||||
let _slot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSlotUpdate>();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
||||
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
||||
@@ -123,3 +123,245 @@ fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
|
||||
assert!(debug.contains("account_filter_count"));
|
||||
assert!(debug.contains("from_slot"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_account_and_slot_filters_encode_complete_current_wire() {
|
||||
let account = ksp_core_lib::Pubkey::new_from_array([1_u8; 32]);
|
||||
let owner = ksp_core_lib::Pubkey::new_from_array([2_u8; 32]);
|
||||
let mut filter = crate::YellowstoneSubscribeAccountFilter::new();
|
||||
assert!(filter.push_account(account).is_ok());
|
||||
assert!(filter.push_owner(owner).is_ok());
|
||||
let raw = crate::YellowstoneAccountMemcmp::bytes(4, vec![1_u8, 2, 3]).expect("raw memcmp must validate");
|
||||
let base58 = crate::YellowstoneAccountMemcmp::base58(8, "1234").expect("base58 memcmp must validate");
|
||||
let base64 = crate::YellowstoneAccountMemcmp::base64(12, "AQID==").expect("base64 memcmp must validate");
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(raw)).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(base58)).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(base64)).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::DataSize(165)).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::TokenAccountState(true)).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Eq(1))).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Ne(2))).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Lt(3))).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Gt(4))).is_ok());
|
||||
filter.set_nonempty_txn_signature(std::option::Option::Some(true));
|
||||
let cuckoo =
|
||||
crate::YellowstoneCuckooFilter::new(vec![9_u8; 16], 4, 4, 8, 77, crate::YellowstoneCuckooHashAlgorithm::SipHash).expect("cuckoo fixture must validate");
|
||||
filter.set_cuckoo_accounts_filter(std::option::Option::Some(cuckoo));
|
||||
let wire = filter.to_wire();
|
||||
assert_eq!(wire.account, vec![account.to_string()]);
|
||||
assert_eq!(wire.owner, vec![owner.to_string()]);
|
||||
assert_eq!(wire.filters.len(), 9);
|
||||
match wire.filters[0].filter.as_ref().expect("raw memcmp oneof must be present") {
|
||||
yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Memcmp(value) => assert_eq!(
|
||||
value.data,
|
||||
std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_memcmp::Data::Bytes(vec![1_u8, 2, 3]))
|
||||
),
|
||||
_ => panic!("first predicate must stay memcmp"),
|
||||
}
|
||||
match wire.filters[5].filter.as_ref().expect("lamports oneof must be present") {
|
||||
yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Lamports(value) => {
|
||||
assert_eq!(value.cmp, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Eq(1)))
|
||||
},
|
||||
_ => panic!("sixth predicate must stay lamports"),
|
||||
}
|
||||
assert_eq!(wire.nonempty_txn_signature, std::option::Option::Some(true));
|
||||
let cuckoo = wire.cuckoo_accounts_filter.expect("cuckoo filter must be present");
|
||||
assert_eq!(cuckoo.data, vec![9_u8; 16]);
|
||||
assert_eq!(cuckoo.bucket_count, 4);
|
||||
assert_eq!(cuckoo.entries_per_bucket, 4);
|
||||
assert_eq!(cuckoo.fingerprint_bits, 8);
|
||||
assert_eq!(cuckoo.hash_seed, 77);
|
||||
assert_eq!(cuckoo.hash_algorithm, yellowstone_grpc_proto::geyser::CuckooHashAlgorithm::SipHash as i32);
|
||||
let mut slots = crate::YellowstoneSubscribeSlotFilter::new();
|
||||
slots.set_filter_by_commitment(std::option::Option::Some(false));
|
||||
slots.set_interslot_updates(std::option::Option::Some(true));
|
||||
assert_eq!(slots.filter_by_commitment(), std::option::Option::Some(false));
|
||||
assert_eq!(slots.interslot_updates(), std::option::Option::Some(true));
|
||||
let slots_wire = slots.to_wire();
|
||||
assert_eq!(slots_wire.filter_by_commitment, std::option::Option::Some(false));
|
||||
assert_eq!(slots_wire.interslot_updates, std::option::Option::Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_account_filter_bounds_and_debug_are_provider_neutral() {
|
||||
assert!(crate::YellowstoneCuckooFilter::new(vec![], 0, 4, 8, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
||||
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 0, 8, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
||||
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 4, 7, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
||||
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 4, 12, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_ok());
|
||||
assert!(crate::YellowstoneAccountMemcmp::base58(0, "contains whitespace").is_err());
|
||||
assert!(crate::YellowstoneAccountMemcmp::base64(0, "line\nbreak").is_err());
|
||||
let memcmp = crate::YellowstoneAccountMemcmp::bytes(5, vec![7_u8, 8, 9]).expect("memcmp must validate");
|
||||
let debug = format!("{memcmp:?}");
|
||||
assert!(debug.contains("payload_length"));
|
||||
assert!(!debug.contains("7, 8, 9"));
|
||||
let mut filter = crate::YellowstoneSubscribeAccountFilter::new();
|
||||
assert!(filter.push_account(ksp_core_lib::Pubkey::new_from_array([3_u8; 32])).is_ok());
|
||||
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(memcmp)).is_ok());
|
||||
let filter_debug = format!("{filter:?}");
|
||||
assert!(filter_debug.contains("account_count"));
|
||||
assert!(!filter_debug.contains(&ksp_core_lib::Pubkey::new_from_array([3_u8; 32]).to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_account_update_decodes_complete_wire_and_redacts_payload_debug() {
|
||||
let signature = vec![6_u8; 64];
|
||||
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["accounts-main".to_owned(), "accounts-owner".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
||||
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
||||
pubkey: vec![1_u8; 32],
|
||||
lamports: 42,
|
||||
owner: vec![2_u8; 32],
|
||||
executable: true,
|
||||
rent_epoch: 9,
|
||||
data: vec![0xAA_u8, 0xBB, 0xCC],
|
||||
write_version: 7,
|
||||
txn_signature: std::option::Option::Some(signature),
|
||||
}),
|
||||
slot: 123,
|
||||
is_startup: true,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 1_700_000_000, nanos: 123_456_789 }),
|
||||
};
|
||||
let update = super::decode_account_update(wire).expect("account update fixture must decode");
|
||||
assert_eq!(update.filters()[0].as_str(), "accounts-main");
|
||||
assert_eq!(update.filters()[1].as_str(), "accounts-owner");
|
||||
assert_eq!(update.created_at().expect("timestamp must be present").seconds(), 1_700_000_000);
|
||||
assert_eq!(update.created_at().expect("timestamp must be present").nanos(), 123_456_789);
|
||||
assert_eq!(update.slot(), 123);
|
||||
assert!(update.is_startup());
|
||||
assert_eq!(update.account().pubkey(), &ksp_core_lib::Pubkey::new_from_array([1_u8; 32]));
|
||||
assert_eq!(update.account().owner(), &ksp_core_lib::Pubkey::new_from_array([2_u8; 32]));
|
||||
assert_eq!(update.account().lamports(), 42);
|
||||
assert!(update.account().executable());
|
||||
assert_eq!(update.account().rent_epoch(), 9);
|
||||
assert_eq!(update.account().data(), &[0xAA_u8, 0xBB, 0xCC]);
|
||||
assert_eq!(update.account().write_version(), 7);
|
||||
assert_eq!(update.account().transaction_signature().expect("signature must be present").as_bytes(), &[6_u8; 64]);
|
||||
let debug = format!("{update:?}");
|
||||
assert!(!debug.contains("accounts-main"));
|
||||
assert!(!debug.contains("170, 187, 204"));
|
||||
assert!(!debug.contains(&ksp_core_lib::Pubkey::new_from_array([1_u8; 32]).to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_account_update_rejects_malformed_fixed_width_and_envelope_fields() {
|
||||
let malformed_pubkey = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["accounts".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
||||
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
||||
pubkey: vec![1_u8; 31],
|
||||
lamports: 0,
|
||||
owner: vec![2_u8; 32],
|
||||
executable: false,
|
||||
rent_epoch: 0,
|
||||
data: vec![],
|
||||
write_version: 0,
|
||||
txn_signature: std::option::Option::None,
|
||||
}),
|
||||
slot: 0,
|
||||
is_startup: false,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_account_update(malformed_pubkey).is_err());
|
||||
let malformed_signature = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["accounts".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
||||
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
||||
pubkey: vec![1_u8; 32],
|
||||
lamports: 0,
|
||||
owner: vec![2_u8; 32],
|
||||
executable: false,
|
||||
rent_epoch: 0,
|
||||
data: vec![],
|
||||
write_version: 0,
|
||||
txn_signature: std::option::Option::Some(vec![9_u8; 63]),
|
||||
}),
|
||||
slot: 0,
|
||||
is_startup: false,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_account_update(malformed_signature).is_err());
|
||||
let missing_info = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["accounts".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount { account: std::option::Option::None, slot: 0, is_startup: false },
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_account_update(missing_info).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_slot_update_preserves_all_current_statuses_and_bounds_dead_error() {
|
||||
let statuses = [
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotProcessed, crate::YellowstoneSlotStatus::Processed),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotConfirmed, crate::YellowstoneSlotStatus::Confirmed),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotFinalized, crate::YellowstoneSlotStatus::Finalized),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotFirstShredReceived, crate::YellowstoneSlotStatus::FirstShredReceived),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotCompleted, crate::YellowstoneSlotStatus::Completed),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotCreatedBank, crate::YellowstoneSlotStatus::CreatedBank),
|
||||
(yellowstone_grpc_proto::geyser::SlotStatus::SlotDead, crate::YellowstoneSlotStatus::Dead),
|
||||
];
|
||||
for (wire_status, expected) in statuses {
|
||||
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["slots".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
||||
slot: 88,
|
||||
parent: std::option::Option::Some(87),
|
||||
status: wire_status as i32,
|
||||
dead_error: if expected == crate::YellowstoneSlotStatus::Dead {
|
||||
std::option::Option::Some("fork rejected".to_owned())
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 5, nanos: 6 }),
|
||||
};
|
||||
let update = super::decode_slot_update(wire).expect("slot update fixture must decode");
|
||||
assert_eq!(update.status(), expected);
|
||||
assert_eq!(update.slot(), 88);
|
||||
assert_eq!(update.parent(), std::option::Option::Some(87));
|
||||
assert_eq!(update.filters()[0].as_str(), "slots");
|
||||
if expected == crate::YellowstoneSlotStatus::Dead {
|
||||
assert_eq!(update.dead_error(), std::option::Option::Some("fork rejected"));
|
||||
assert!(!format!("{update:?}").contains("fork rejected"));
|
||||
}
|
||||
}
|
||||
let unknown = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["slots".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
||||
slot: 1,
|
||||
parent: std::option::Option::None,
|
||||
status: 99,
|
||||
dead_error: std::option::Option::None,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_slot_update(unknown).is_err());
|
||||
let oversized = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["slots".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
||||
slot: 1,
|
||||
parent: std::option::Option::None,
|
||||
status: yellowstone_grpc_proto::geyser::SlotStatus::SlotDead as i32,
|
||||
dead_error: std::option::Option::Some("x".repeat(16 * 1024 + 1)),
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_slot_update(oversized).is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user