367 lines
12 KiB
Rust
367 lines
12 KiB
Rust
// 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<ksp_core_lib::ErrorCode>,
|
|
}
|
|
|
|
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<ksp_core_lib::ErrorCode>,
|
|
) -> 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<ksp_core_lib::ErrorCode> {
|
|
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<crate::WsSubscriptionSnapshot>,
|
|
}
|
|
|
|
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<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<crate::WsSubscriptionSnapshot>,
|
|
) -> 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;
|