// file: crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs // version: 7 /// Stable local identity assigned to one physical WebSocket session. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WsSessionId(std::num::NonZeroU64); impl WsSessionId { /// Creates a session identity from a non-zero local value. #[must_use] pub const fn new(value: std::num::NonZeroU64) -> Self { return Self(value); } /// Returns the stable local numeric value. #[must_use] pub const fn get(self) -> u64 { return self.0.get(); } } /// Stable local identity assigned to one logical WebSocket subscription. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WsSubscriptionId(std::num::NonZeroU64); impl WsSubscriptionId { /// Creates a subscription identity from a non-zero local value. #[must_use] pub const fn new(value: std::num::NonZeroU64) -> Self { return Self(value); } /// Returns the stable local numeric value. #[must_use] pub const fn get(self) -> u64 { return self.0.get(); } } /// Observable lifecycle state of one physical WebSocket session. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum WsSessionState { /// No physical connection is currently active and no connection attempt is running. Disconnected, /// The actor is establishing the physical connection. Connecting, /// The physical connection is active. Active, /// The actor is reconnecting after an unexpected physical disconnect. Reconnecting { /// One-based reconnect attempt currently in progress or waiting for backoff. attempt: u32, }, /// Explicit shutdown has started and new subscriptions are refused. Closing, /// Explicit shutdown completed. Closed, /// The session reached a terminal failure state. Failed, } /// Observable lifecycle state of one logical WebSocket subscription. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum WsSubscriptionState { /// The local subscription exists but its initial subscribe request has not completed. Requested, /// The logical subscription is bound to an active remote subscription. Active, /// The logical subscription is being restored after reconnect. Resubscribing, /// Local cancellation has won and remote cleanup is in progress when possible. Cancelling, /// The logical subscription reached a non-error terminal state. Closed, /// The logical subscription reached a terminal failure state. Failed, } /// WebSocket subscription family represented by one logical subscription. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum WsSubscriptionKind { /// `accountSubscribe` family. Account, /// `blockSubscribe` family. Block, /// `logsSubscribe` family. Logs, /// `programSubscribe` family. Program, /// `rootSubscribe` family. Root, /// `signatureSubscribe` family. Signature, /// `slotSubscribe` family. Slot, /// `slotsUpdatesSubscribe` family. SlotsUpdates, /// `voteSubscribe` family. Vote, /// Helius LaserStream WebSocket `transactionSubscribe` extension family. HeliusTransaction, } impl WsSubscriptionKind { /// Returns the stable KSP descriptor for this WebSocket subscription family. #[must_use] pub const fn as_str(self) -> &'static str { return match self { Self::Account => "account", Self::Block => "block", Self::Logs => "logs", Self::Program => "program", Self::Root => "root", Self::Signature => "signature", Self::Slot => "slot", Self::SlotsUpdates => "slots_updates", Self::Vote => "vote", Self::HeliusTransaction => "helius_transaction", }; } /// Returns the exact subscribe JSON-RPC method for this family. pub(crate) const fn subscribe_method(self) -> &'static str { return match self { Self::Account => "accountSubscribe", Self::Block => "blockSubscribe", Self::Logs => "logsSubscribe", Self::Program => "programSubscribe", Self::Root => "rootSubscribe", Self::Signature => "signatureSubscribe", Self::Slot => "slotSubscribe", Self::SlotsUpdates => "slotsUpdatesSubscribe", Self::Vote => "voteSubscribe", Self::HeliusTransaction => "transactionSubscribe", }; } /// Returns the exact unsubscribe JSON-RPC method for this family. pub(crate) const fn unsubscribe_method(self) -> &'static str { return match self { Self::Account => "accountUnsubscribe", Self::Block => "blockUnsubscribe", Self::Logs => "logsUnsubscribe", Self::Program => "programUnsubscribe", Self::Root => "rootUnsubscribe", Self::Signature => "signatureUnsubscribe", Self::Slot => "slotUnsubscribe", Self::SlotsUpdates => "slotsUpdatesUnsubscribe", Self::Vote => "voteUnsubscribe", Self::HeliusTransaction => "transactionUnsubscribe", }; } /// Returns the exact notification method emitted for this family. pub(crate) const fn notification_method(self) -> &'static str { return match self { Self::Account => "accountNotification", Self::Block => "blockNotification", Self::Logs => "logsNotification", Self::Program => "programNotification", Self::Root => "rootNotification", Self::Signature => "signatureNotification", Self::Slot => "slotNotification", Self::SlotsUpdates => "slotsUpdatesNotification", Self::Vote => "voteNotification", Self::HeliusTransaction => "transactionNotification", }; } /// Returns whether Solana documents this standard subscription family as unstable. /// /// Provider extensions are stable here unless explicitly classified otherwise. pub(crate) const fn is_unstable(self) -> bool { return matches!(self, Self::Block | Self::SlotsUpdates | Self::Vote); } /// Emits the centralized KSP warning required before opening an unstable standard subscription. pub(crate) fn warn_if_unstable(self) { if self.is_unstable() { ksp_logging_lib::warn!( target: crate::TRACING_TARGET, rpc_method = self.subscribe_method(), subscription_kind = self.as_str(), documentation_status = "unstable", "unstable Solana WebSocket subscription requested" ); } return; } } /// Safe lifecycle projection for one logical WebSocket subscription. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WsSubscriptionSnapshot { id: crate::WsSubscriptionId, kind: crate::WsSubscriptionKind, state: crate::WsSubscriptionState, remote_bound: bool, terminal_error_code: std::option::Option, } impl WsSubscriptionSnapshot { /// Creates one safe subscription lifecycle projection for Transport runtime internals. #[must_use] pub(crate) const fn new( id: crate::WsSubscriptionId, kind: crate::WsSubscriptionKind, state: crate::WsSubscriptionState, remote_bound: bool, terminal_error_code: std::option::Option, ) -> Self { return Self { id, kind, state, remote_bound, terminal_error_code }; } /// Returns the stable local subscription identity. #[must_use] pub const fn id(&self) -> crate::WsSubscriptionId { return self.id; } /// Returns the logical WebSocket subscription family. #[must_use] pub const fn kind(&self) -> crate::WsSubscriptionKind { return self.kind; } /// Returns the current logical lifecycle state. #[must_use] pub const fn state(&self) -> crate::WsSubscriptionState { return self.state; } /// Returns whether a current remote subscription ID is bound internally. /// /// The remote ID itself is deliberately absent because it is ephemeral across reconnects. #[must_use] pub const fn remote_bound(&self) -> bool { return self.remote_bound; } /// Returns the safe terminal error code when this subscription ended because of a failure. #[must_use] pub const fn terminal_error_code(&self) -> std::option::Option { return self.terminal_error_code; } } /// Safe runtime snapshot for one physical WebSocket session. /// /// The snapshot deliberately contains logical endpoint metadata and local identities only. It never stores the endpoint URL, credentials, request payloads or /// raw notifications. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WsSessionSnapshot { id: crate::WsSessionId, endpoint_name: std::string::String, provider: crate::WsProviderName, cluster: crate::WsClusterName, protocol: crate::WsProtocolKind, state: crate::WsSessionState, pending_request_count: usize, continuity_gap_count: u64, overflow_count: u64, subscriptions: std::vec::Vec, } impl WsSessionSnapshot { /// Creates one safe session projection for Transport runtime internals. #[must_use] #[allow(clippy::too_many_arguments)] pub(crate) fn new( id: crate::WsSessionId, endpoint_name: impl std::convert::Into, provider: crate::WsProviderName, cluster: crate::WsClusterName, protocol: crate::WsProtocolKind, state: crate::WsSessionState, pending_request_count: usize, continuity_gap_count: u64, overflow_count: u64, subscriptions: std::vec::Vec, ) -> Self { return Self { id, endpoint_name: endpoint_name.into(), provider, cluster, protocol, state, pending_request_count, continuity_gap_count, overflow_count, subscriptions, }; } /// Returns the stable local session identity. #[must_use] pub const fn id(&self) -> crate::WsSessionId { return self.id; } /// Returns the safe logical endpoint name. #[must_use] pub fn endpoint_name(&self) -> &str { return self.endpoint_name.as_str(); } /// Returns the provider descriptor without endpoint credentials. #[must_use] pub const fn provider(&self) -> &crate::WsProviderName { return &self.provider; } /// Returns the cluster descriptor. #[must_use] pub const fn cluster(&self) -> &crate::WsClusterName { return &self.cluster; } /// Returns the WebSocket protocol family. #[must_use] pub const fn protocol(&self) -> crate::WsProtocolKind { return self.protocol; } /// Returns the current physical session lifecycle state. #[must_use] pub const fn state(&self) -> crate::WsSessionState { return self.state; } /// Returns the number of JSON-RPC requests currently awaiting responses. #[must_use] pub const fn pending_request_count(&self) -> usize { return self.pending_request_count; } /// Returns the number of observed physical continuity gaps for this session. #[must_use] pub const fn continuity_gap_count(&self) -> u64 { return self.continuity_gap_count; } /// Returns the cumulative number of notification queue overflows observed by this session. #[must_use] pub const fn overflow_count(&self) -> u64 { return self.overflow_count; } /// Returns safe lifecycle projections for logical subscriptions owned by this session. #[must_use] pub fn subscriptions(&self) -> &[crate::WsSubscriptionSnapshot] { return self.subscriptions.as_slice(); } /// Returns the number of logical subscriptions currently projected by the session. #[must_use] pub fn subscription_count(&self) -> usize { return self.subscriptions.len(); } } #[cfg(test)] #[path = "../unit_tests/ws_lifecycle.rs"] mod tests;