v0.3.14-pre.003

This commit is contained in:
2026-09-11 21:54:19 +02:00
parent 7bf938c19f
commit 6721a4142a
15 changed files with 872 additions and 22 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 47
// version: 48
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -555,6 +555,8 @@ pub use self::ws_protocol_session::HeliusLaserStreamWsSession;
pub use self::ws_protocol_session::SolanaStandardWsSession;
/// Shareable compatibility handle for one explicitly created standard Solana physical WebSocket session.
pub use self::ws_session::WsSession;
/// Cloneable latest-value observer for one safe physical WebSocket session snapshot.
pub use self::ws_session::WsSessionSnapshotSource;
/// Open cluster or network descriptor used by WebSocket endpoint settings.
pub use self::ws_settings::WsClusterName;
/// Runtime settings for one named WebSocket endpoint.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
// version: 6
// version: 7
/// Typed facade for one standard Solana WebSocket physical session.
///
@@ -42,6 +42,12 @@ impl SolanaStandardWsSession {
return self.inner.snapshot();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {
@@ -117,6 +123,12 @@ impl HeliusLaserStreamWsSession {
return self.inner.snapshot();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_session.rs
// version: 14
// version: 15
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -10,6 +10,43 @@ const HELIUS_WS_HEARTBEAT_INTERVAL: std::time::Duration = std::time::Duration::f
type WsPhysicalStream = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
/// Cloneable latest-value observer for one physical WebSocket session snapshot.
///
/// The observer exposes only the already-safe [`crate::WsSessionSnapshot`] projection and keeps the internal Tokio watch channel private. Cloning it does
/// not create another socket, actor, reconnect loop or subscription registry.
#[derive(Clone)]
pub struct WsSessionSnapshotSource {
receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>,
}
impl crate::WsSessionSnapshotSource {
fn new(receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>) -> Self {
return Self { receiver };
}
/// Returns the current safe physical-session snapshot without waiting for another actor transition.
#[must_use]
pub fn current(&self) -> crate::WsSessionSnapshot {
return (*self.receiver.borrow()).clone();
}
/// Waits for one newer safe physical-session snapshot.
///
/// `None` means the owning WebSocket actor dropped the latest-value publisher and no further snapshot can arrive.
pub async fn wait_for_change(&mut self) -> std::option::Option<crate::WsSessionSnapshot> {
return match self.receiver.changed().await {
std::result::Result::Ok(()) => std::option::Option::Some((*self.receiver.borrow_and_update()).clone()),
std::result::Result::Err(_) => std::option::Option::None,
};
}
}
impl std::fmt::Debug for crate::WsSessionSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("WsSessionSnapshotSource").field("current", &self.current()).finish();
}
}
/// Shareable handle for one explicitly created physical WebSocket session.
///
/// The handle never exposes the sensitive endpoint URL or the underlying socket. All socket I/O is owned by one internal actor task and all caller
@@ -124,6 +161,12 @@ impl WsSession {
return self.snapshot_rx.borrow().clone();
}
/// Returns a cloneable latest-value observer for safe physical-session snapshots.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return crate::WsSessionSnapshotSource::new(self.snapshot_rx.clone());
}
/// Returns the latest observable physical-session state.
#[must_use]
pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 52
// version: 53
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -569,6 +569,17 @@ fn public_v0_2_7_pre_004_physical_websocket_session_contract_is_available_from_c
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_WS_SESSION_CLOSED.code(), "ws_session_closed");
}
#[test]
fn public_v0_3_14_pre_003_websocket_snapshot_source_is_available_from_crate_root() {
let _physical = ksp_onchain_transport_lib::WsSession::snapshot_source;
let _standard = ksp_onchain_transport_lib::SolanaStandardWsSession::snapshot_source;
let _helius = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::snapshot_source;
let _current = ksp_onchain_transport_lib::WsSessionSnapshotSource::current;
let _wait = ksp_onchain_transport_lib::WsSessionSnapshotSource::wait_for_change;
assert!(std::any::type_name::<ksp_onchain_transport_lib::WsSessionSnapshotSource>().ends_with("WsSessionSnapshotSource"));
return;
}
#[test]
fn public_v0_2_7_pre_005_bounded_websocket_close_contract_is_available_from_crate_root() {
let _close = ksp_onchain_transport_lib::WsSession::close;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 44
// version: 45
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
@@ -1354,3 +1354,23 @@ fn release_v0_3_13_pre_002_yellowstone_subscribe_identity_remains_opaque_and_dep
assert!(!root.contains("pub use yellowstone_grpc_proto"));
return;
}
#[test]
fn release_v0_3_14_pre_003_ws_snapshot_source_reuses_actor_watch_without_second_runtime() {
let session_source = include_str!("../src/ws_session.rs");
let facade_source = include_str!("../src/ws_protocol_session.rs");
let session_tests = include_str!("../unit_tests/ws_session.rs");
assert!(session_source.contains("pub struct WsSessionSnapshotSource"));
assert!(session_source.contains("self.snapshot_rx.clone()"));
assert!(facade_source.matches("pub fn snapshot_source").count() >= 2);
for required in [
"v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime",
"websocket_reconnect_resubscribes_in_local_id_order_and_remaps_remote_ids",
"websocket_notification_queue_overflow_fails_only_slow_subscription_and_cleans_remote_binding",
] {
assert!(session_tests.contains(required), "missing pre.003 WebSocket continuity test: {required}");
}
assert!(!session_source.contains("WsSessionSnapshotSource {\n socket:"));
assert!(!facade_source.contains("tokio_tungstenite::connect_async"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
// version: 11
// version: 12
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -1355,3 +1355,40 @@ async fn websocket_dropped_notification_receiver_triggers_remote_cleanup_and_rel
session.close().await.expect("session close must remain bounded");
server.await.expect("local server task must complete");
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime() {
let session_id = crate::WsSessionId::new(std::num::NonZeroU64::new(1).expect("test session ID must be non-zero"));
let initial = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Active,
0,
0,
0,
std::vec::Vec::new(),
);
let reconnecting = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Reconnecting { attempt: 1 },
0,
1,
0,
std::vec::Vec::new(),
);
let (sender, receiver) = tokio::sync::watch::channel(initial.clone());
let mut source = crate::WsSessionSnapshotSource::new(receiver);
assert_eq!(source.current(), initial);
sender.send_replace(reconnecting.clone());
assert_eq!(source.wait_for_change().await, std::option::Option::Some(reconnecting));
drop(sender);
assert_eq!(source.wait_for_change().await, std::option::Option::None);
return;
}