v0.2.8-pre.008
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 231
|
# version: 232
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.8-pre.7.fix.4"
|
version = "0.2.8-pre.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
|
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ fn helius_transaction_subscribe_params(request: &crate::HeliusTransactionSubscri
|
|||||||
///
|
///
|
||||||
/// The nested transaction payload is deliberately retained as JSON because its exact Solana wire representation depends on the requested encoding and detail
|
/// The nested transaction payload is deliberately retained as JSON because its exact Solana wire representation depends on the requested encoding and detail
|
||||||
/// mode. KSP types the stable provider envelope while preserving the full nested payload without Program-specific decoding.
|
/// mode. KSP types the stable provider envelope while preserving the full nested payload without Program-specific decoding.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct HeliusFullTransactionNotification {
|
pub struct HeliusFullTransactionNotification {
|
||||||
transaction: serde_json::Value,
|
transaction: serde_json::Value,
|
||||||
signature: std::string::String,
|
signature: std::string::String,
|
||||||
@@ -382,8 +382,20 @@ impl HeliusFullTransactionNotification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for HeliusFullTransactionNotification {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter
|
||||||
|
.debug_struct("HeliusFullTransactionNotification")
|
||||||
|
.field("transaction", &"<omitted>")
|
||||||
|
.field("signature", &"<omitted>")
|
||||||
|
.field("slot", &self.slot)
|
||||||
|
.field("transaction_index", &self.transaction_index)
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Signatures-mode notification delivered by Helius `transactionSubscribe`.
|
/// Signatures-mode notification delivered by Helius `transactionSubscribe`.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct HeliusTransactionSignatureNotification {
|
pub struct HeliusTransactionSignatureNotification {
|
||||||
signature: std::string::String,
|
signature: std::string::String,
|
||||||
slot: u64,
|
slot: u64,
|
||||||
@@ -438,11 +450,36 @@ impl HeliusTransactionSignatureNotification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for HeliusTransactionSignatureNotification {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter
|
||||||
|
.debug_struct("HeliusTransactionSignatureNotification")
|
||||||
|
.field("signature", &"<omitted>")
|
||||||
|
.field("slot", &self.slot)
|
||||||
|
.field("transaction_index", &self.transaction_index)
|
||||||
|
.field("err", &wire_field_debug_state(&self.err))
|
||||||
|
.field("memo", &wire_field_debug_state(&self.memo))
|
||||||
|
.field("block_time", &wire_field_debug_state(&self.block_time))
|
||||||
|
.field("confirmation_status", &wire_field_debug_state(&self.confirmation_status))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wire_field_debug_state<T>(field: &crate::SolanaWireField<T>) -> &'static str {
|
||||||
|
if field.is_omitted() {
|
||||||
|
return "omitted";
|
||||||
|
}
|
||||||
|
if field.is_null() {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
return "value";
|
||||||
|
}
|
||||||
|
|
||||||
/// Typed Helius `transactionNotification` payload union.
|
/// Typed Helius `transactionNotification` payload union.
|
||||||
///
|
///
|
||||||
/// `Full` also covers the provider `accounts` detail mode because both contain the nested `transaction` member. `Signature` covers the lightweight
|
/// `Full` also covers the provider `accounts` detail mode because both contain the nested `transaction` member. `Signature` covers the lightweight
|
||||||
/// signatures mode. `Unknown` preserves `none` mode and forward-compatible provider shapes instead of failing the logical subscription.
|
/// signatures mode. `Unknown` preserves `none` mode and forward-compatible provider shapes instead of failing the logical subscription.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum HeliusTransactionNotification {
|
pub enum HeliusTransactionNotification {
|
||||||
/// Full/accounts notification carrying the nested transaction payload.
|
/// Full/accounts notification carrying the nested transaction payload.
|
||||||
@@ -453,6 +490,16 @@ pub enum HeliusTransactionNotification {
|
|||||||
Unknown(serde_json::Value),
|
Unknown(serde_json::Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for HeliusTransactionNotification {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return match self {
|
||||||
|
Self::Full(notification) => formatter.debug_tuple("Full").field(notification).finish(),
|
||||||
|
Self::Signature(notification) => formatter.debug_tuple("Signature").field(notification).finish(),
|
||||||
|
Self::Unknown(_) => formatter.debug_tuple("Unknown").field(&"<omitted>").finish(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::HeliusLaserStreamWsSession {
|
impl crate::HeliusLaserStreamWsSession {
|
||||||
/// Opens one Helius `transactionSubscribe` logical subscription through the shared physical actor.
|
/// Opens one Helius `transactionSubscribe` logical subscription through the shared physical actor.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
|
// file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
/// Typed facade for one standard Solana WebSocket physical session.
|
/// Typed facade for one standard Solana WebSocket physical session.
|
||||||
///
|
///
|
||||||
/// The facade delegates to the same [`crate::WsSession`] actor used by the compatibility API. It owns no socket, registry, reconnect loop or queue of its
|
/// The facade delegates to the same [`crate::WsSession`] actor used by the compatibility API. It owns no socket, registry, reconnect loop or queue of its
|
||||||
/// own and therefore does not duplicate the physical WebSocket runtime. Subscription wrappers are implemented beside their wire owners in the
|
/// own and therefore does not duplicate the physical WebSocket runtime. Subscription wrappers are implemented beside their wire owners in the
|
||||||
/// `ws_accounts`, `ws_blocks`, `ws_cluster` and `ws_transactions` modules.
|
/// `ws_accounts`, `ws_blocks`, `ws_cluster` and `ws_transactions` modules.
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// async fn unsupported_helius_transaction(
|
||||||
|
/// session: &ksp_onchain_transport_lib::SolanaStandardWsSession,
|
||||||
|
/// request: &ksp_onchain_transport_lib::HeliusTransactionSubscribeRequest,
|
||||||
|
/// ) {
|
||||||
|
/// let _ = session.transaction_subscribe(request).await;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SolanaStandardWsSession {
|
pub struct SolanaStandardWsSession {
|
||||||
inner: crate::WsSession,
|
inner: crate::WsSession,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
// version: 29
|
// version: 30
|
||||||
|
|
||||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||||
|
|
||||||
@@ -808,7 +808,7 @@ fn release_v0_2_8_pre_003_helius_surface_is_exactly_six_standard_families_before
|
|||||||
assert!(source.contains("unsupported_block"));
|
assert!(source.contains("unsupported_block"));
|
||||||
assert!(source.contains("unsupported_slots_updates"));
|
assert!(source.contains("unsupported_slots_updates"));
|
||||||
assert!(source.contains("unsupported_vote"));
|
assert!(source.contains("unsupported_vote"));
|
||||||
assert!(!source.contains("transaction_subscribe"));
|
assert!(!source.contains("pub async fn transaction_subscribe"));
|
||||||
assert_eq!(ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream.as_str(), "helius_laserstream");
|
assert_eq!(ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream.as_str(), "helius_laserstream");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,3 +865,24 @@ fn release_v0_2_8_pre_007_helius_heartbeat_is_provider_owned_by_shared_actor_onl
|
|||||||
let protocol_source = include_str!("../src/ws_protocol_session.rs");
|
let protocol_source = include_str!("../src/ws_protocol_session.rs");
|
||||||
assert!(!protocol_source.contains("heartbeat_interval"));
|
assert!(!protocol_source.contains("heartbeat_interval"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_8_pre_008_adversarial_guards_preserve_provider_isolation_and_safe_diagnostics() {
|
||||||
|
let protocol_source = include_str!("../src/ws_protocol_session.rs");
|
||||||
|
assert!(protocol_source.contains("unsupported_helius_transaction"));
|
||||||
|
assert!(protocol_source.contains("unsupported_block"));
|
||||||
|
assert!(protocol_source.contains("unsupported_slots_updates"));
|
||||||
|
assert!(protocol_source.contains("unsupported_vote"));
|
||||||
|
let helius_source = include_str!("../src/ws_helius_transactions.rs");
|
||||||
|
assert!(helius_source.contains("impl std::fmt::Debug for HeliusFullTransactionNotification"));
|
||||||
|
assert!(helius_source.contains("impl std::fmt::Debug for HeliusTransactionSignatureNotification"));
|
||||||
|
assert!(helius_source.contains("impl std::fmt::Debug for HeliusTransactionNotification"));
|
||||||
|
assert!(helius_source.contains("Self::Unknown(_)"));
|
||||||
|
assert!(helius_source.contains("<omitted>"));
|
||||||
|
let actor_source = include_str!("../src/ws_session.rs");
|
||||||
|
assert!(actor_source.contains("max_message_size_bytes"));
|
||||||
|
assert!(actor_source.contains("max_frame_size_bytes"));
|
||||||
|
assert!(actor_source.contains("WsNotificationDispatchOutcome::QueueFull"));
|
||||||
|
assert!(actor_source.contains("ERROR_CODE_WS_BACKPRESSURE_OVERFLOW"));
|
||||||
|
assert!(actor_source.contains("remote_to_local.remove"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
use futures_util::SinkExt; // rust-rules: trait-import
|
use futures_util::SinkExt; // rust-rules: trait-import
|
||||||
use futures_util::StreamExt; // rust-rules: trait-import
|
use futures_util::StreamExt; // rust-rules: trait-import
|
||||||
@@ -78,6 +78,23 @@ fn backpressure_session_settings() -> crate::WsSessionSettings {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn adversarial_payload_session_settings() -> crate::WsSessionSettings {
|
||||||
|
let defaults = crate::WsSessionSettings::default();
|
||||||
|
return crate::WsSessionSettings::new(
|
||||||
|
std::time::Duration::from_millis(250),
|
||||||
|
std::time::Duration::from_millis(200),
|
||||||
|
crate::WsReconnectSettings::new(2, std::time::Duration::from_millis(20), std::time::Duration::from_millis(20)),
|
||||||
|
crate::WsResubscribePolicy::ActiveSubscriptions,
|
||||||
|
defaults.command_queue_capacity(),
|
||||||
|
defaults.notification_queue_capacity(),
|
||||||
|
defaults.max_active_subscriptions(),
|
||||||
|
defaults.max_pending_requests(),
|
||||||
|
256,
|
||||||
|
128,
|
||||||
|
defaults.max_write_buffer_size_bytes(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async fn bind_local_listener() -> (tokio::net::TcpListener, std::string::String) {
|
async fn bind_local_listener() -> (tokio::net::TcpListener, std::string::String) {
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("local listener must bind");
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("local listener must bind");
|
||||||
let address = listener.local_addr().expect("local listener must expose address");
|
let address = listener.local_addr().expect("local listener must expose address");
|
||||||
@@ -97,6 +114,19 @@ async fn send_result(websocket: &mut tokio_tungstenite::WebSocketStream<tokio::n
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_error(
|
||||||
|
websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
|
||||||
|
request: &serde_json::Value,
|
||||||
|
code: i64,
|
||||||
|
message: &str,
|
||||||
|
data: serde_json::Value,
|
||||||
|
) {
|
||||||
|
let id = request.get("id").and_then(serde_json::Value::as_u64).expect("request id must be numeric");
|
||||||
|
let response = serde_json::json!({"jsonrpc":"2.0","id":id,"error":{"code":code,"message":message,"data":data}});
|
||||||
|
websocket.send(tokio_tungstenite::tungstenite::Message::Text(response.to_string().into())).await.expect("local error response must send");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_notification(websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>, subscription: u64, result: serde_json::Value) {
|
async fn send_notification(websocket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>, subscription: u64, result: serde_json::Value) {
|
||||||
let notification = serde_json::json!({"jsonrpc":"2.0","method":"transactionNotification","params":{"subscription":subscription,"result":result}});
|
let notification = serde_json::json!({"jsonrpc":"2.0","method":"transactionNotification","params":{"subscription":subscription,"result":result}});
|
||||||
websocket.send(tokio_tungstenite::tungstenite::Message::Text(notification.to_string().into())).await.expect("local notification must send");
|
websocket.send(tokio_tungstenite::tungstenite::Message::Text(notification.to_string().into())).await.expect("local notification must send");
|
||||||
@@ -115,7 +145,7 @@ async fn send_root_notification(websocket: &mut tokio_tungstenite::WebSocketStre
|
|||||||
async fn wait_for_gap_count(session: &crate::HeliusLaserStreamWsSession, expected: u64) {
|
async fn wait_for_gap_count(session: &crate::HeliusLaserStreamWsSession, expected: u64) {
|
||||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||||
loop {
|
loop {
|
||||||
if session.snapshot().continuity_gap_count() >= expected {
|
if session.snapshot().continuity_gap_count() >= expected && session.state() == crate::WsSessionState::Active {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert!(tokio::time::Instant::now() < deadline, "Helius session continuity gap count must advance before timeout");
|
assert!(tokio::time::Instant::now() < deadline, "Helius session continuity gap count must advance before timeout");
|
||||||
@@ -134,6 +164,17 @@ async fn wait_for_overflow_count(session: &crate::HeliusLaserStreamWsSession, ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_session_subscription_count(session: &crate::HeliusLaserStreamWsSession, expected: usize) {
|
||||||
|
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||||
|
loop {
|
||||||
|
if session.snapshot().subscription_count() == expected {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert!(tokio::time::Instant::now() < deadline, "Helius session subscription count must settle before timeout");
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn wait_for_subscription_state<T>(subscription: &crate::WsSubscription<T>, expected: crate::WsSubscriptionState) {
|
async fn wait_for_subscription_state<T>(subscription: &crate::WsSubscription<T>, expected: crate::WsSubscriptionState) {
|
||||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||||
loop {
|
loop {
|
||||||
@@ -380,6 +421,157 @@ fn helius_transaction_filter_debug_omits_signature_and_account_values() {
|
|||||||
assert!(!debug.contains("Vote111111111111111111111111111111111111111"));
|
assert!(!debug.contains("Vote111111111111111111111111111111111111111"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn helius_transaction_notification_debug_omits_raw_provider_payloads() {
|
||||||
|
let full_value = serde_json::json!({
|
||||||
|
"transaction":{"raw":"MASSIVE-RAW-PAYLOAD-CANARY"},
|
||||||
|
"signature":"FULL-SIGNATURE-CANARY",
|
||||||
|
"slot":77,
|
||||||
|
"transactionIndex":3
|
||||||
|
});
|
||||||
|
let full = super::decode_helius_transaction_notification(full_value).expect("full Helius notification must decode");
|
||||||
|
let signature_value = serde_json::json!({
|
||||||
|
"signature":"SIGNATURE-MODE-CANARY",
|
||||||
|
"slot":78,
|
||||||
|
"transactionIndex":4,
|
||||||
|
"err":{"secret":"ERROR-DATA-CANARY"},
|
||||||
|
"memo":"MEMO-CANARY",
|
||||||
|
"blockTime":123,
|
||||||
|
"confirmationStatus":"CONFIRMATION-CANARY"
|
||||||
|
});
|
||||||
|
let signature = super::decode_helius_transaction_notification(signature_value).expect("signature Helius notification must decode");
|
||||||
|
let unknown = super::decode_helius_transaction_notification(serde_json::json!({"provider":"UNKNOWN-PAYLOAD-CANARY"}))
|
||||||
|
.expect("unknown Helius notification must remain forward-compatible");
|
||||||
|
let rendered = format!("{full:?} {signature:?} {unknown:?}");
|
||||||
|
assert!(rendered.contains("transaction: \"<omitted>\""));
|
||||||
|
assert!(rendered.contains("signature: \"<omitted>\""));
|
||||||
|
assert!(rendered.contains("err: \"value\""));
|
||||||
|
assert!(rendered.contains("memo: \"value\""));
|
||||||
|
assert!(rendered.contains("Unknown(\"<omitted>\")"));
|
||||||
|
for forbidden in [
|
||||||
|
"MASSIVE-RAW-PAYLOAD-CANARY",
|
||||||
|
"FULL-SIGNATURE-CANARY",
|
||||||
|
"SIGNATURE-MODE-CANARY",
|
||||||
|
"ERROR-DATA-CANARY",
|
||||||
|
"MEMO-CANARY",
|
||||||
|
"CONFIRMATION-CANARY",
|
||||||
|
"UNKNOWN-PAYLOAD-CANARY",
|
||||||
|
] {
|
||||||
|
assert!(!rendered.contains(forbidden));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn helius_provider_rpc_application_error_is_safe_and_does_not_fail_session() {
|
||||||
|
let (listener, url) = bind_local_listener().await;
|
||||||
|
let server = tokio::spawn(async move {
|
||||||
|
let (stream, _) = listener.accept().await.expect("local server must accept Helius client");
|
||||||
|
let mut websocket = tokio_tungstenite::accept_async(stream).await.expect("local Helius handshake must succeed");
|
||||||
|
let transaction_subscribe = read_request(&mut websocket).await;
|
||||||
|
assert_eq!(transaction_subscribe["method"], serde_json::json!("transactionSubscribe"));
|
||||||
|
send_error(
|
||||||
|
&mut websocket,
|
||||||
|
&transaction_subscribe,
|
||||||
|
-32602,
|
||||||
|
"PROVIDER-MESSAGE-SECRET-CANARY",
|
||||||
|
serde_json::json!({"apiKey":"PROVIDER-ERROR-SECRET-CANARY","payload":"X".repeat(4096)}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let root_subscribe = read_request(&mut websocket).await;
|
||||||
|
assert_eq!(root_subscribe["method"], serde_json::json!("rootSubscribe"));
|
||||||
|
send_result(&mut websocket, &root_subscribe, serde_json::json!(72)).await;
|
||||||
|
send_root_notification(&mut websocket, 72, 88).await;
|
||||||
|
let root_unsubscribe = read_request(&mut websocket).await;
|
||||||
|
assert_eq!(root_unsubscribe["method"], serde_json::json!("rootUnsubscribe"));
|
||||||
|
assert_eq!(root_unsubscribe["params"], serde_json::json!([72]));
|
||||||
|
send_result(&mut websocket, &root_unsubscribe, serde_json::json!(true)).await;
|
||||||
|
wait_for_close_frame(&mut websocket).await;
|
||||||
|
});
|
||||||
|
let endpoint_url = format!("{url}/?api-key=HELIUS-ENDPOINT-SECRET-CANARY");
|
||||||
|
let session = crate::HeliusLaserStreamWsSession::connect(helius_endpoint(endpoint_url.as_str())).await.expect("Helius facade must connect");
|
||||||
|
let request = crate::HeliusTransactionSubscribeRequest::new(base_filter(), std::option::Option::None);
|
||||||
|
let error = session.transaction_subscribe(&request).await.expect_err("provider application error must reject only the logical subscribe request");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
|
||||||
|
assert!(error.context().iter().any(|entry| return entry.key() == "rpc_code" && entry.value() == "-32602"));
|
||||||
|
assert!(error.context().iter().any(|entry| return entry.key() == "method" && entry.value() == "transactionSubscribe"));
|
||||||
|
let rendered = format!("{error:?} {error} {session:?} {:?}", session.snapshot());
|
||||||
|
for forbidden in ["PROVIDER-MESSAGE-SECRET-CANARY", "PROVIDER-ERROR-SECRET-CANARY", "HELIUS-ENDPOINT-SECRET-CANARY", "fixture-signature-secret-canary"] {
|
||||||
|
assert!(!rendered.contains(forbidden));
|
||||||
|
}
|
||||||
|
assert_eq!(session.state(), crate::WsSessionState::Active);
|
||||||
|
wait_for_session_subscription_count(&session, 0).await;
|
||||||
|
let mut root = session.root_subscribe().await.expect("session must accept a healthy subscription after provider application error");
|
||||||
|
assert_eq!(root.recv().await.expect("healthy root notification must arrive").expect("healthy root notification must decode"), 88);
|
||||||
|
assert!(root.unsubscribe().await.expect("healthy root unsubscribe must complete"));
|
||||||
|
session.close().await.expect("Helius fixture session must close");
|
||||||
|
server.await.expect("provider error fixture server must finish");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn helius_notification_method_mismatch_fails_only_transaction_subscription() {
|
||||||
|
let (listener, url) = bind_local_listener().await;
|
||||||
|
let server = tokio::spawn(async move {
|
||||||
|
let (stream, _) = listener.accept().await.expect("local server must accept Helius client");
|
||||||
|
let mut websocket = tokio_tungstenite::accept_async(stream).await.expect("local Helius handshake must succeed");
|
||||||
|
let transaction_subscribe = read_request(&mut websocket).await;
|
||||||
|
send_result(&mut websocket, &transaction_subscribe, serde_json::json!(41)).await;
|
||||||
|
let root_subscribe = read_request(&mut websocket).await;
|
||||||
|
send_result(&mut websocket, &root_subscribe, serde_json::json!(42)).await;
|
||||||
|
send_root_notification(&mut websocket, 41, 5).await;
|
||||||
|
let cleanup = read_request(&mut websocket).await;
|
||||||
|
assert_eq!(cleanup["method"], serde_json::json!("transactionUnsubscribe"));
|
||||||
|
assert_eq!(cleanup["params"], serde_json::json!([41]));
|
||||||
|
send_result(&mut websocket, &cleanup, serde_json::json!(true)).await;
|
||||||
|
send_root_notification(&mut websocket, 42, 99).await;
|
||||||
|
wait_for_close_frame(&mut websocket).await;
|
||||||
|
});
|
||||||
|
let session = crate::HeliusLaserStreamWsSession::connect(helius_endpoint(url.as_str())).await.expect("Helius facade must connect");
|
||||||
|
let request = crate::HeliusTransactionSubscribeRequest::new(crate::HeliusTransactionSubscribeFilter::default(), std::option::Option::None);
|
||||||
|
let mut transaction = session.transaction_subscribe(&request).await.expect("transaction subscription must register");
|
||||||
|
let mut root = session.root_subscribe().await.expect("root subscription must register");
|
||||||
|
wait_for_subscription_state(&transaction, crate::WsSubscriptionState::Failed).await;
|
||||||
|
assert_eq!(transaction.terminal_error_code(), std::option::Option::Some(crate::ERROR_CODE_WS_PROTOCOL_ERROR));
|
||||||
|
assert!(transaction.recv().await.is_none());
|
||||||
|
assert_eq!(session.state(), crate::WsSessionState::Active);
|
||||||
|
wait_for_session_subscription_count(&session, 1).await;
|
||||||
|
assert_eq!(root.recv().await.expect("healthy root notification must arrive").expect("healthy root notification must decode"), 99);
|
||||||
|
assert_eq!(root.state(), crate::WsSubscriptionState::Active);
|
||||||
|
session.close().await.expect("Helius fixture session must close");
|
||||||
|
server.await.expect("notification mismatch fixture server must finish");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn helius_oversized_inbound_payload_reconnects_before_provider_json_decode() {
|
||||||
|
let (listener, url) = bind_local_listener().await;
|
||||||
|
let server = tokio::spawn(async move {
|
||||||
|
let (first_stream, _) = listener.accept().await.expect("initial Helius client must connect");
|
||||||
|
let mut first = tokio_tungstenite::accept_async(first_stream).await.expect("initial Helius handshake must succeed");
|
||||||
|
first
|
||||||
|
.send(tokio_tungstenite::tungstenite::Message::Text("PROVIDER-PAYLOAD-CANARY".repeat(32).into()))
|
||||||
|
.await
|
||||||
|
.expect("oversized provider fixture payload must send");
|
||||||
|
let (replacement_stream, _) = listener.accept().await.expect("replacement Helius client must connect");
|
||||||
|
let mut replacement = tokio_tungstenite::accept_async(replacement_stream).await.expect("replacement Helius handshake must succeed");
|
||||||
|
let root_subscribe = read_request(&mut replacement).await;
|
||||||
|
assert_eq!(root_subscribe["method"], serde_json::json!("rootSubscribe"));
|
||||||
|
send_result(&mut replacement, &root_subscribe, serde_json::json!(91)).await;
|
||||||
|
let root_unsubscribe = read_request(&mut replacement).await;
|
||||||
|
assert_eq!(root_unsubscribe["method"], serde_json::json!("rootUnsubscribe"));
|
||||||
|
send_result(&mut replacement, &root_unsubscribe, serde_json::json!(true)).await;
|
||||||
|
wait_for_close_frame(&mut replacement).await;
|
||||||
|
});
|
||||||
|
let session = crate::HeliusLaserStreamWsSession::connect(helius_endpoint_with_session(url.as_str(), adversarial_payload_session_settings()))
|
||||||
|
.await
|
||||||
|
.expect("Helius facade must connect before adversarial payload");
|
||||||
|
wait_for_gap_count(&session, 1).await;
|
||||||
|
assert_eq!(session.state(), crate::WsSessionState::Active);
|
||||||
|
assert_eq!(session.snapshot().continuity_gap_count(), 1);
|
||||||
|
let mut root = session.root_subscribe().await.expect("recovered Helius session must remain usable");
|
||||||
|
assert!(root.unsubscribe().await.expect("recovered root subscription must unsubscribe"));
|
||||||
|
session.close().await.expect("recovered Helius session must close");
|
||||||
|
server.await.expect("oversized provider payload fixture server must finish");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "current_thread")]
|
#[tokio::test(flavor = "current_thread")]
|
||||||
async fn helius_transaction_live_handle_decodes_notification_and_unsubscribes_through_shared_actor() {
|
async fn helius_transaction_live_handle_decodes_notification_and_unsubscribes_through_shared_actor() {
|
||||||
let (listener, url) = bind_local_listener().await;
|
let (listener, url) = bind_local_listener().await;
|
||||||
|
|||||||
238
deltas/0.2.8/pre.008.md
Normal file
238
deltas/0.2.8/pre.008.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
<!-- file: deltas/0.2.8/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.8-pre.008` — adversarial Helius provider + limites + diagnostics sûrs
|
||||||
|
|
||||||
|
## 1. Base et objet
|
||||||
|
|
||||||
|
Base appliquée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.8-pre.7.fix.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Le checkpoint opérateur final de `pre.007` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all OK
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py clean
|
||||||
|
cargo check --workspace OK
|
||||||
|
cargo clippy --workspace --all-targets OK
|
||||||
|
Transport unit 331/331
|
||||||
|
Transport public API 40/40
|
||||||
|
Transport release completeness 29/29
|
||||||
|
Transport doctests compile-fail 4/4
|
||||||
|
cargo test --workspace OK
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` durcit la surface Helius WebSocket contre des comportements provider adversariaux sans créer de nouveau runtime. La tranche réutilise l'actor physique, les limites frame/message, le reconnect, le registry et la backpressure existants.
|
||||||
|
|
||||||
|
## 2. Version technique
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.8-pre.8
|
||||||
|
commit attendu = v0.2.8-pre.008
|
||||||
|
Git tag = aucun tag prerelease
|
||||||
|
```
|
||||||
|
|
||||||
|
Le header root `Cargo.toml` passe en version `232`.
|
||||||
|
|
||||||
|
## 3. Ré-audit Helius courant
|
||||||
|
|
||||||
|
La documentation Helius réauditée le 2026-08-23 confirme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
transactionSubscribe supporté
|
||||||
|
transactionUnsubscribe supporté
|
||||||
|
accountInclude/accountExclude/accountRequired 50 000 adresses max par liste
|
||||||
|
blockSubscribe non supporté Helius
|
||||||
|
slotsUpdatesSubscribe non supporté Helius
|
||||||
|
voteSubscribe non supporté Helius
|
||||||
|
endpoints WSS unifiés mainnet/devnet
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche n'introduit donc aucune nouvelle famille provider.
|
||||||
|
|
||||||
|
## 4. `Debug` sûr pour les notifications Helius
|
||||||
|
|
||||||
|
Le prompt `0.2.8` interdit les payloads arbitraires/massifs dans les diagnostics génériques. Avant cette tranche, les types de notification Helius dérivaient `Debug`, ce qui pouvait rendre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
transaction JSON brut
|
||||||
|
signature complète
|
||||||
|
memo/error provider
|
||||||
|
fallback Unknown(serde_json::Value) brut
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` remplace ces dérivations par des implémentations explicites :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HeliusFullTransactionNotification
|
||||||
|
transaction <omitted>
|
||||||
|
signature <omitted>
|
||||||
|
slot visible
|
||||||
|
transaction_index visible
|
||||||
|
|
||||||
|
HeliusTransactionSignatureNotification
|
||||||
|
signature <omitted>
|
||||||
|
slot/index visibles
|
||||||
|
err omitted/null/value seulement
|
||||||
|
memo omitted/null/value seulement
|
||||||
|
block_time omitted/null/value seulement
|
||||||
|
confirmation_status omitted/null/value seulement
|
||||||
|
|
||||||
|
HeliusTransactionNotification::Unknown
|
||||||
|
payload Value conservé fonctionnellement
|
||||||
|
Debug = Unknown("<omitted>")
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun accessor fonctionnel n'est supprimé. La donnée reste accessible explicitement au consommateur qui la demande ; seul le chemin diagnostic implicite est redacted.
|
||||||
|
|
||||||
|
## 5. Capability guard inverse
|
||||||
|
|
||||||
|
La façade Helius avait déjà quatre doctests compile-fail :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Helius -X-> blockSubscribe
|
||||||
|
Helius -X-> slotsUpdatesSubscribe
|
||||||
|
Helius -X-> voteSubscribe
|
||||||
|
Helius -X-> into_inner
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` ajoute le canari inverse :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaStandardWsSession -X-> transactionSubscribe
|
||||||
|
```
|
||||||
|
|
||||||
|
La séparation standard/provider est donc prouvée dans les deux sens sans exposer le `WsSession` générique.
|
||||||
|
|
||||||
|
## 6. Provider RPC application error
|
||||||
|
|
||||||
|
Un serveur local adversarial retourne une erreur JSON-RPC `transactionSubscribe` avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
code provider
|
||||||
|
message canary sensible
|
||||||
|
payload data arbitraire de plusieurs KiB
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari exige :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ERROR_CODE_RPC_APPLICATION_ERROR
|
||||||
|
context sûr = rpc_code + method
|
||||||
|
message/data provider non copiés dans KspError
|
||||||
|
api-key URL non rendue
|
||||||
|
filter signature non rendue
|
||||||
|
session physique reste Active
|
||||||
|
subscription_count revient à 0
|
||||||
|
rootSubscribe fonctionne ensuite normalement
|
||||||
|
```
|
||||||
|
|
||||||
|
Une erreur d'application provider ne doit donc pas être promue en panne de transport.
|
||||||
|
|
||||||
|
## 7. Notification method mismatch
|
||||||
|
|
||||||
|
Un remote ID enregistré comme `HeliusTransaction` reçoit volontairement un `rootNotification`.
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
logical transaction subscription -> Failed / WS_PROTOCOL_ERROR
|
||||||
|
remote binding transaction -> cleanup transactionUnsubscribe
|
||||||
|
session physique -> Active
|
||||||
|
subscription root saine -> Active et notification reçue
|
||||||
|
```
|
||||||
|
|
||||||
|
Le mismatch de famille ne doit pas contaminer les autres logical subscriptions.
|
||||||
|
|
||||||
|
## 8. Payload entrant oversized
|
||||||
|
|
||||||
|
Un endpoint Helius local envoie une frame texte supérieure aux limites configurées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
max_message_size_bytes = 256
|
||||||
|
max_frame_size_bytes = 128
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari prouve que la limite tungstenite/actor s'applique avant tout décodage JSON provider, déclenche le reconnect borné existant et laisse la connexion Helius de remplacement utilisable pour un `rootSubscribe`/unsubscribe normal.
|
||||||
|
|
||||||
|
Aucun nouveau compteur ou chemin de reconnect n'est ajouté.
|
||||||
|
|
||||||
|
## 9. Backpressure et lifecycle conservés
|
||||||
|
|
||||||
|
La preuve provider-specific de `pre.006` reste autoritaire pour :
|
||||||
|
|
||||||
|
```text
|
||||||
|
queue capacity = 1
|
||||||
|
transaction notification overflow
|
||||||
|
échec de la seule transaction subscription
|
||||||
|
cleanup transactionUnsubscribe
|
||||||
|
root subscription saine non affectée
|
||||||
|
session physique Active
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.008` ne duplique pas ce scénario. Les canaris partagés hérités continuent aussi de couvrir unsubscribe pendant reconnect, decode failure isolé, pending bounds et shutdown pendant backoff.
|
||||||
|
|
||||||
|
## 10. Canaris ajoutés
|
||||||
|
|
||||||
|
Quatre unit tests Helius sont ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
notification Debug redaction Full/Signature/Unknown
|
||||||
|
provider RPC application error sûre + session réutilisable
|
||||||
|
notification method mismatch isolé + cleanup transactionUnsubscribe
|
||||||
|
oversized inbound provider payload + reconnect + session réutilisable
|
||||||
|
```
|
||||||
|
|
||||||
|
Un release-completeness canary supplémentaire vérifie les guards, les `Debug` explicites et la réutilisation des mécanismes de bornage de l'actor.
|
||||||
|
|
||||||
|
Un doctest compile-fail supplémentaire interdit `transactionSubscribe` sur `SolanaStandardWsSession`.
|
||||||
|
|
||||||
|
Comptages attendus :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport unit 335
|
||||||
|
Transport public API 40
|
||||||
|
release completeness 30
|
||||||
|
doctests compile-fail 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md
|
||||||
|
docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md
|
||||||
|
deltas/0.2.8/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Hors scope
|
||||||
|
|
||||||
|
Restent hors de `pre.008` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
compliance finale Helius + standard/HTTP pre.009
|
||||||
|
smoke Helius live + cargo tree final pre.010
|
||||||
|
fermeture docs/indexes + prompt 0.2.9 pre.011
|
||||||
|
LaserStream gRPC futur backend séparé
|
||||||
|
Gatekeeper/preconf WebSocket hors scope 0.2.8
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Gate opérateur
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Critère de fermeture : zéro warning, audit clean, **335 unit / 40 public API / 30 completeness / 5 doctests** et workspace vert.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
|
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
|
||||||
<!-- version: 21 -->
|
<!-- version: 22 -->
|
||||||
|
|
||||||
# Plan `0.2.8` — Helius LaserStream WebSocket
|
# Plan `0.2.8` — Helius LaserStream WebSocket
|
||||||
|
|
||||||
> **Statut : `0.2.8-pre.007-fix.003` ferme enfin le heartbeat Transport à 331/331, avec fmt/audit/check/Clippy verts. Le workspace révèle toutefois un canari de dépendances devenu obsolète : `ksp-core-lib/tests/workspace_dependencies.rs` attend encore l’ancienne déclaration Tokio dev `net, rt` alors que `pre.007` a ajouté `io-util, test-util` pour les tests d’horloge. `0.2.8-pre.007-fix.004` aligne ce canari workspace sans toucher au runtime heartbeat.**
|
> **Statut : `0.2.8-pre.007-fix.004` est validé intégralement par l’opérateur : fmt/audit/check/Clippy, 331 unit Transport, 40 API, 29 completeness, 4 doctests et workspace complet verts. `0.2.8-pre.008` est préparé pour les guards adversariaux provider, limites/payload, isolation de session et redaction des diagnostics Helius.**
|
||||||
|
|
||||||
## 1. Objet, base et état courant
|
## 1. Objet, base et état courant
|
||||||
|
|
||||||
@@ -34,14 +34,15 @@ pre.005 contrat typed transactionSubscribe/unsubscribe validé
|
|||||||
pre.005-fix.001 correction types de canaris + visibilité/tests/règles validée
|
pre.005-fix.001 correction types de canaris + visibilité/tests/règles validée
|
||||||
pre.005-fix.002 suppression warnings dead_code via cfg(test) validée
|
pre.005-fix.002 suppression warnings dead_code via cfg(test) validée
|
||||||
pre.006 transactionNotification + lifecycle actor validé
|
pre.006 transactionNotification + lifecycle actor validé
|
||||||
pre.007 heartbeat Helius WebSocket / idle — checkpoint gate à corriger
|
pre.007 heartbeat Helius WebSocket / idle validé
|
||||||
pre.007-fix.001 lint + close canary corrigés ; un canari périodique reste non déterministe
|
pre.007-fix.001 lint + close canary corrigés
|
||||||
pre.007-fix.002 observation yield-only insuffisante — checkpoint 330/331
|
pre.007-fix.002 observation yield-only insuffisante — checkpoint historique
|
||||||
pre.007-fix.003 heartbeat Transport fermé ; workspace bloqué par canari Tokio dev obsolète
|
pre.007-fix.003 heartbeat Transport fermé
|
||||||
pre.007-fix.004 alignement canari dependency-firewall Tokio dev — préparé
|
pre.007-fix.004 alignement canari dependency-firewall Tokio dev validé
|
||||||
|
pre.008 adversarial provider/capabilities/payload/security — préparé
|
||||||
|
|
||||||
workspace.package.version courant = 0.2.8-pre.7.fix.4
|
workspace.package.version courant = 0.2.8-pre.8
|
||||||
commit attendu = v0.2.8-pre.007-fix.004
|
commit attendu = v0.2.8-pre.008
|
||||||
aucun tag prerelease
|
aucun tag prerelease
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,12 +72,12 @@ pre.005 DONE — transactionSubscribe request typed + filters/options/tokenAcco
|
|||||||
fix.002 DONE — helpers wire non consommés en production bornés à #[cfg(test)] ; zéro warning dead_code
|
fix.002 DONE — helpers wire non consommés en production bornés à #[cfg(test)] ; zéro warning dead_code
|
||||||
pre.006 DONE — transactionNotification + handle live + actor registry/reconnect/resubscribe/unsubscribe races
|
pre.006 DONE — transactionNotification + handle live + actor registry/reconnect/resubscribe/unsubscribe races
|
||||||
+ late notifications + backpressure ciblée, sans second actor/socket
|
+ late notifications + backpressure ciblée, sans second actor/socket
|
||||||
pre.007 FIX REQUIRED — heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown
|
pre.007 DONE — heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown
|
||||||
fix.001 CHECKPOINT — lint et close canary corrigés ; 330/331 unit
|
fix.001 DONE — lint et close canary corrigés
|
||||||
fix.002 CHECKPOINT — observation yield-only insuffisante ; 330/331 unit
|
fix.002 DONE — diagnostic de propagation scheduler établi
|
||||||
fix.003 CHECKPOINT — heartbeat Transport fermé à 331/331 ; workspace échoue sur canari Tokio dev obsolète
|
fix.003 DONE — canari heartbeat périodique stabilisé sans changer le runtime
|
||||||
fix.004 PREPARED — alignement exact du dependency-firewall sur `io-util, net, rt, test-util`
|
fix.004 DONE — dependency-firewall aligné sur `io-util, net, rt, test-util`
|
||||||
pre.008 provider adversarial lifecycle + capability guards + payload/backpressure + security/redaction
|
pre.008 PREPARED — provider adversarial lifecycle + capability guards + payload/backpressure + security/redaction
|
||||||
pre.009 compliance Helius WebSocket + non-régressions Solana standard 18/18 + HTTP 52/14
|
pre.009 compliance Helius WebSocket + non-régressions Solana standard 18/18 + HTTP 52/14
|
||||||
+ Config/API/dependency-firewall canaries
|
+ Config/API/dependency-firewall canaries
|
||||||
pre.010 smoke Helius WebSocket live opt-in si stratégie sûre + README/USAGE
|
pre.010 smoke Helius WebSocket live opt-in si stratégie sûre + README/USAGE
|
||||||
@@ -1314,3 +1315,84 @@ transport_manifest_preserves_ksp_dependency_firewall
|
|||||||
|
|
||||||
`pre.008` reste bloqué jusqu'au replay workspace vert de `pre.007-fix.004`.
|
`pre.008` reste bloqué jusqu'au replay workspace vert de `pre.007-fix.004`.
|
||||||
|
|
||||||
|
## 20. Fermeture `pre.007` et préparation `pre.008`
|
||||||
|
|
||||||
|
Checkpoint opérateur `0.2.8-pre.7.fix.4` reçu le 2026-08-23 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
[x] cargo fmt --all
|
||||||
|
[x] python3 scripts/audit_rust_workspace_rules.py = clean / 0 candidate
|
||||||
|
[x] cargo check --workspace = vert, sans warning
|
||||||
|
[x] cargo clippy --workspace --all-targets = vert, sans warning
|
||||||
|
[x] cargo test -p ksp-onchain-transport-lib = 331 unit + 40 API + 29 completeness + 4 doctests
|
||||||
|
[x] cargo test --workspace = vert
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche heartbeat et ses quatre fixes sont donc fermés. `pre.008` ne rouvre ni cadence, ni Config, ni lifecycle transaction nominal.
|
||||||
|
|
||||||
|
Ré-audit Helius du 2026-08-23 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
transactionSubscribe supporté sur endpoints WSS unifiés mainnet/devnet
|
||||||
|
accountInclude/accountExclude/accountRequired 50 000 adresses max chacune
|
||||||
|
transactionUnsubscribe ID distant numérique + bool de réponse
|
||||||
|
blockSubscribe non supporté Helius
|
||||||
|
slotsUpdatesSubscribe non supporté Helius
|
||||||
|
voteSubscribe non supporté Helius
|
||||||
|
```
|
||||||
|
|
||||||
|
Le runtime partagé possède déjà les mécanismes de bornage : `max_message_size_bytes`, `max_frame_size_bytes`, queues de notifications bornées, reconnect fini, remapping remote/local et isolation de logical subscription. Le travail `pre.008` porte donc principalement sur des canaris adversariaux Helius et sur un défaut de diagnostic identifié pendant la revue : les notifications Helius publiques dérivaient encore `Debug` sur leur payload brut.
|
||||||
|
|
||||||
|
Décisions `pre.008` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. HeliusFullTransactionNotification::Debug
|
||||||
|
- transaction brute omise
|
||||||
|
- signature omise
|
||||||
|
- slot/index seulement conservés comme métadonnées sûres
|
||||||
|
|
||||||
|
2. HeliusTransactionSignatureNotification::Debug
|
||||||
|
- signature/memo/err/confirmation value omis
|
||||||
|
- états omitted/null/value seulement pour les champs wire optionnels
|
||||||
|
|
||||||
|
3. HeliusTransactionNotification::Unknown
|
||||||
|
- payload serde_json::Value toujours accessible via la variante
|
||||||
|
- Debug rend uniquement <omitted>
|
||||||
|
|
||||||
|
4. provider JSON-RPC application error
|
||||||
|
- ERROR_CODE_RPC_APPLICATION_ERROR
|
||||||
|
- rpc_code + method sûrs seulement
|
||||||
|
- message/data arbitraires provider absents du KspError
|
||||||
|
- session physique reste Active et accepte une subscription saine ensuite
|
||||||
|
|
||||||
|
5. notification method mismatch
|
||||||
|
- remote ID Helius lié à transactionNotification recevant une autre méthode
|
||||||
|
- seule la logical transaction subscription échoue
|
||||||
|
- cleanup transactionUnsubscribe best-effort
|
||||||
|
- autre subscription saine et session physique restent actives
|
||||||
|
|
||||||
|
6. oversized provider payload
|
||||||
|
- max frame/message partagé appliqué avant JSON provider
|
||||||
|
- reconnect borné du même actor
|
||||||
|
- connexion de remplacement reste utilisable
|
||||||
|
|
||||||
|
7. capability inverse
|
||||||
|
- Helius garde compile-fail block/slotsUpdates/vote
|
||||||
|
- SolanaStandard ajoute compile-fail transactionSubscribe
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune nouvelle dépendance, aucun nouveau socket/actor, aucune extension Config, aucune lecture d'environnement Transport. Les garanties `pre.006` de queue overflow transaction + root restent la preuve provider-specific de backpressure ciblée ; `pre.008` ne duplique pas ce test inutilement.
|
||||||
|
|
||||||
|
Gate attendu après application :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport unit 335
|
||||||
|
Transport public API 40
|
||||||
|
release completeness 30
|
||||||
|
Transport doctests 5
|
||||||
|
workspace vert
|
||||||
|
warnings 0
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.009` reste bloqué jusqu'au replay intégralement vert de `pre.008`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
|
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
|
||||||
<!-- version: 20 -->
|
<!-- version: 21 -->
|
||||||
|
|
||||||
# Validation `0.2.8` — Helius LaserStream WebSocket
|
# Validation `0.2.8` — Helius LaserStream WebSocket
|
||||||
|
|
||||||
@@ -838,3 +838,59 @@ Critères `pre.007-fix.004` :
|
|||||||
|
|
||||||
Verdict courant : **`pre.007` FIX REQUIRED ; `fix.001` CHECKPOINT ; `fix.002` CHECKPOINT ; `fix.003` CHECKPOINT Transport vert ; `fix.004` PREPARED ; `pre.008` bloqué.**
|
Verdict courant : **`pre.007` FIX REQUIRED ; `fix.001` CHECKPOINT ; `fix.002` CHECKPOINT ; `fix.003` CHECKPOINT Transport vert ; `fix.004` PREPARED ; `pre.008` bloqué.**
|
||||||
|
|
||||||
|
## 22. Fermeture `pre.007-fix.004` et gate `pre.008`
|
||||||
|
|
||||||
|
Résultat opérateur final `0.2.8-pre.7.fix.4` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
[x] cargo fmt --all
|
||||||
|
[x] audit Rust workspace = clean / 0 candidate
|
||||||
|
[x] cargo check --workspace = vert, sans warning
|
||||||
|
[x] cargo clippy --workspace --all-targets = vert, sans warning
|
||||||
|
[x] Transport unit = 331/331
|
||||||
|
[x] Transport public API = 40/40
|
||||||
|
[x] release completeness = 29/29
|
||||||
|
[x] Transport doctests = 4/4
|
||||||
|
[x] cargo test --workspace = vert
|
||||||
|
```
|
||||||
|
|
||||||
|
Verdict : **`pre.007` DONE avec `fix.001..004` DONE. `pre.008` peut être ouvert.**
|
||||||
|
|
||||||
|
Ré-audit provider courant : Helius documente toujours `transactionSubscribe` sur les endpoints WSS unifiés mainnet/devnet, les trois listes d'adresses à 50 000 max et l'absence de support de `blockSubscribe`, `slotsUpdatesSubscribe` et `voteSubscribe`.
|
||||||
|
|
||||||
|
Critères `pre.008` préparés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ] workspace.package.version = 0.2.8-pre.8
|
||||||
|
[ ] aucune nouvelle dépendance
|
||||||
|
[ ] aucun second actor/socket/provider registry
|
||||||
|
[ ] SolanaStandard ne peut pas appeler transactionSubscribe (compile-fail)
|
||||||
|
[ ] Helius ne peut toujours pas appeler block/slotsUpdates/vote (compile-fail)
|
||||||
|
[ ] provider RPC application error ne tue pas la session physique
|
||||||
|
[ ] provider message/data arbitraires ne sont pas copiés dans KspError
|
||||||
|
[ ] mismatch notification method échoue seulement la logical transaction subscription
|
||||||
|
[ ] cleanup du mismatch utilise transactionUnsubscribe
|
||||||
|
[ ] autre subscription saine survit au mismatch
|
||||||
|
[ ] oversized inbound provider payload déclenche le reconnect borné avant JSON decode métier
|
||||||
|
[ ] session Helius de remplacement reste utilisable après oversized payload
|
||||||
|
[ ] queue overflow transaction reste isolée (preuve pre.006 conservée)
|
||||||
|
[ ] Debug Full notification omet transaction et signature brutes
|
||||||
|
[ ] Debug Signature notification omet signature/memo/error values
|
||||||
|
[ ] Debug Unknown notification omet le serde_json::Value brut
|
||||||
|
[ ] endpoint api-key/filter values restent absents session/request/snapshot diagnostics
|
||||||
|
[ ] remote subscription id reste absent des snapshots publics
|
||||||
|
[ ] standard WebSocket 18/18 inchangé
|
||||||
|
[ ] HTTP 52 current + 14 historical inchangé
|
||||||
|
[ ] Config V1/V2 et helius_laserstream inchangés
|
||||||
|
[ ] cargo fmt --all
|
||||||
|
[ ] audit Rust = clean
|
||||||
|
[ ] cargo check = sans warning
|
||||||
|
[ ] cargo clippy = sans warning
|
||||||
|
[ ] Transport = 335 unit + 40 API + 30 completeness + 5 doctests
|
||||||
|
[ ] cargo test --workspace = vert
|
||||||
|
```
|
||||||
|
|
||||||
|
La correction `Debug` ne retire aucune donnée de l'API fonctionnelle : `transaction()`, `signature()`, les accessors wire et la variante `Unknown(Value)` restent disponibles explicitement. Elle empêche seulement les diagnostics génériques de rendre un payload provider arbitraire ou massif.
|
||||||
|
|
||||||
|
Verdict courant : **`pre.008` PREPARED ; `pre.009` bloqué jusqu'au gate opérateur.**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user