31 KiB
Validation 0.2.7 — WebSocket Solana standard
Statut : matrice active,
0.2.7-pre.004. Settings/lifecyclepre.002, Config V2pre.003et première session physique actor-ownedpre.004sont matérialisés. Registry subscriptions, shutdown complet, reconnect et wrappers typed restent ouverts.
1. Baseline normative
Audit officiel effectué le 22 août 2026 contre :
https://solana.com/docs/rpc/websocket
Compte exact de l'index courant :
9 subscribe
9 unsubscribe
18 méthodes WebSocket totales
Répartition statut KSP :
12 méthodes appartenant à 6 paires documentées non marquées unstable/deprecated
6 méthodes appartenant à 3 paires unstable : block, slotsUpdates, vote
0 méthode de l'index courant marquée Deprecated
Pour une paire unstable, l'unsubscribe associé est classé Unstable pair dans KSP même si sa propre page n'affiche pas nécessairement le bandeau, car il n'existe que pour annuler la subscription unstable correspondante.
2. Matrice exhaustive des 18 opérations
| # | Méthode | Type | Statut pre.001 |
Paramètres / résultat essentiels | Notification / paire | Stratégie de test | Source officielle | Compliance |
|---|---|---|---|---|---|---|---|---|
| 1 | accountSubscribe |
subscribe | Stable/documented | pubkey ; config commitment, encoding, dataSlice ; result numeric id ; minContextSlot upstream actuellement ignoré, donc non promis |
accountNotification |
fixture encodings/config + subscribe/notify | https://solana.com/docs/rpc/websocket/accountsubscribe |
Planned pre.009 |
| 2 | accountUnsubscribe |
unsubscribe | Stable/documented | remote id ; true or RPC error unknown id |
account pair | handle local -> remote id fixture | https://solana.com/docs/rpc/websocket/accountunsubscribe |
Planned pre.009 |
| 3 | blockSubscribe |
subscribe | Unstable | all/mentions filter ; confirmed/finalized ; encoding ; tx details ; max tx version ; showRewards |
blockNotification |
all options + null block/error + validator capability fixture | https://solana.com/docs/rpc/websocket/blocksubscribe |
Planned pre.011 |
| 4 | blockUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | block pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/blockunsubscribe |
Planned pre.011 |
| 5 | logsSubscribe |
subscribe | Stable/documented | all, allWithVotes, exactly one mentions; commitment |
logsNotification |
3 filters + invalid multi-mention + notification | https://solana.com/docs/rpc/websocket/logssubscribe |
Planned pre.009 |
| 6 | logsUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | logs pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/logsunsubscribe |
Planned pre.009 |
| 7 | programSubscribe |
subscribe | Stable/documented | program pubkey ; commitment ; filters ; encoding ; dataSlice ; withContext |
programNotification |
contexted/non-contexted fixtures + filters | https://solana.com/docs/rpc/websocket/programsubscribe |
Planned pre.009 |
| 8 | programUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | program pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/programunsubscribe |
Planned pre.009 |
| 9 | rootSubscribe |
subscribe | Stable/documented | no params ; numeric id | rootNotification => u64 |
exact root fixture | https://solana.com/docs/rpc/websocket/rootsubscribe |
Planned pre.010 |
| 10 | rootUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | root pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/rootunsubscribe |
Planned pre.010 |
| 11 | signatureSubscribe |
subscribe | Stable/documented | first transaction signature ; commitment ; enableReceivedNotification |
signatureNotification early string or terminal error object |
early + terminal + auto-close/no-resubscribe | https://solana.com/docs/rpc/websocket/signaturesubscribe |
Planned pre.010 |
| 12 | signatureUnsubscribe |
unsubscribe | Stable/documented | remote id before terminal fire ; boolean/error | signature pair | cancel before terminal + stale after terminal | https://solana.com/docs/rpc/websocket/signatureunsubscribe |
Planned pre.010 |
| 13 | slotSubscribe |
subscribe | Stable/documented | no params ; numeric id | slotNotification {slot,parent,root} |
exact fixture + live smoke candidate | https://solana.com/docs/rpc/websocket/slotsubscribe |
Planned pre.010 |
| 14 | slotUnsubscribe |
unsubscribe | Stable/documented | remote id ; boolean/error | slot pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/slotunsubscribe |
Planned pre.010 |
| 15 | slotsUpdatesSubscribe |
subscribe | Unstable | no params ; numeric id | tagged slotsUpdatesNotification |
each known variant + unknown fallback | https://solana.com/docs/rpc/websocket/slotsupdatessubscribe |
Planned pre.011 |
| 16 | slotsUpdatesUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | slotsUpdates pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/slotsupdatesunsubscribe |
Planned pre.011 |
| 17 | voteSubscribe |
subscribe | Unstable | no params ; validator flag required | voteNotification |
fields + timestamp omitted/null/value + warning | https://solana.com/docs/rpc/websocket/votesubscribe |
Planned pre.011 |
| 18 | voteUnsubscribe |
unsubscribe | Unstable pair | remote id ; boolean/error | vote pair | generic registry unsubscribe | https://solana.com/docs/rpc/websocket/voteunsubscribe |
Planned pre.011 |
3. Notification matrix
| Subscribe | Notification | Shape à préserver | Point lossless / lifecycle |
|---|---|---|---|
accountSubscribe |
accountNotification |
contextual account payload | reuse account DTOs/encodings/dataSlice |
programSubscribe |
programNotification |
keyed account, documenté avec contexte | accepter contexted/non-contexted à cause de l'écart docs/source audité ; préserver withContext |
logsSubscribe |
logsNotification |
context + {signature, err, logs} |
err nullable ; filtre mentions exactement une adresse |
signatureSubscribe |
signatureNotification |
context + "receivedSignature" ou {err} |
terminal object clôt la subscription ; early string ne la clôt pas |
slotSubscribe |
slotNotification |
{slot,parent,root} |
non-contextual |
rootSubscribe |
rootNotification |
u64 |
non-contextual |
blockSubscribe |
blockNotification |
context + {slot, block, err} |
unstable ; block/err nullable ; variants block selon config |
slotsUpdatesSubscribe |
slotsUpdatesNotification |
tagged union slot lifecycle | unstable ; fallback unknown/raw borné |
voteSubscribe |
voteNotification |
{votePubkey,slots,hash,timestamp,signature} |
unstable/pre-consensus ; timestamp tolerant wire |
Les contexts WebSocket documentés omettent apiVersion. SolanaRpcContext.api_version: Option<String> est compatible avec cette omission.
4. Baselines Git Agave et SIMD
4.1 Hiérarchie de contrôle
La compliance applique le même modèle que la compliance HTTP finale :
documentation Solana actuelle
-> surface publique annoncée
Git Agave tag courant audité v4.2.1
-> implémentation réelle et types wire
SIMD main
-> évolutions Activated, Review, Draft ou Idea à surveiller
Les pages Solana peuvent lier une révision Agave plus ancienne. KSP ne confond donc pas le lien source embarqué dans une page documentaire avec la baseline Git courante. Au 2026-08-22, Agave v4.2.1 est disponible sur Git et devient la baseline d'implémentation de 0.2.7.
Sources Agave ciblées :
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc/src/rpc_pubsub.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc/src/rpc_subscriptions.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-types/src/response.rs
4.2 Constats Agave v4.2.1
Le cross-check du tag courant confirme les 9 subscribe + 9 unsubscribe déjà inventoriés par la documentation publique et ne révèle aucune opération PubSub standard supplémentaire.
Constats ciblés :
accountSubscribe: le type partagéRpcAccountInfoConfigexposemin_context_slot, mais le handler PubSubv4.2.1le destructure en_ // ignored. KSP ne le compte donc pas comme option WebSocket effective ;programSubscribe:RpcProgramAccountsConfig.with_contextest lu et doit rester dans le contrat KSP ; la tolérance contexted/non-contexted reste requise pour ne pas convertir les divergences docs/source/provider en perte wire ;logsSubscribe: le handler accepteall,allWithVotesoumentionset rejette un filtrementionscontenant autre chose qu'exactement une adresse ;signatureSubscribe: le handler utilise la signature,commitmentetenable_received_notification, sans option WebSocket supplémentaire ;blockSubscribe: le handler consomme le jeu d'options documenté et impose un commitment au moinsconfirmed;*Unsubscribe: un ID serveur inconnu produitInvalidParamsdans le handler audité ;voteSubscribe:RpcVote.timestampestOption<UnixTimestamp>, donc optionnel au niveau wire ;slotsUpdatesSubscribe:SlotUpdatepossède toujours les sept variantes courantesFirstShredReceived,Completed,CreatedBank,Frozen,Dead,OptimisticConfirmationetRoot.
Décisions compliance : transmettre programSubscribe.withContext, accepter les formes contextée/non-contextée, ne pas promettre accountSubscribe.minContextSlot tant qu'il est ignoré upstream, conserver vote.timestamp optionnel et garder un fallback borné pour toute future variante SlotUpdate inconnue.
4.3 Audit SIMD ciblé
Aucun SIMD audité n'ajoute actuellement une dixième famille de subscription standard à la surface Agave v4.2.1. Ils modifient en revanche des hypothèses que KSP doit éviter de figer.
| SIMD | Statut au 2026-08-22 | Conséquence WebSocket KSP |
|---|---|---|
0118 Partitioned Epoch Rewards Distribution |
Activated | conserver les DTOs bloc/rewards HTTP déjà lossless, dont numRewardPartitions omitted, null ou valeur |
0291 Commission Rate in Basis Points |
Review | conserver les représentations de commission upstream indépendantes lorsqu'elles sont présentes |
0296 Larger Transaction Size |
Review | proposition jusqu'à 4096 octets : ne pas déduire les limites WS de l'ancienne taille transaction 1232 ; surveiller blockSubscribe |
0298 Bank Hash in Block Footer |
Idea | aucun bankHash spéculatif dans le wire courant ; surveiller le DTO bloc partagé |
0301 parent bank hash |
PR fermé, non mergé | aucun parentBankHash spéculatif ; même conclusion que la compliance HTTP |
0307 Add Block Footer |
Review | aucun footer spéculatif ; réauditer blockSubscribe lorsque l'upstream l'expose réellement |
0326 Alpenglow |
Review | ne pas figer les sémantiques TowerBFT de voteSubscribe ou optimisticConfirmation dans un contrat durable |
0337 Alpenglow Fast Leader Handover Markers |
Review | nouveaux block markers futurs : surveiller shape et taille, sans ajout WS anticipé |
0384 Alpenglow migration |
Review | des notifications RPC commitment/optimistic confirmation peuvent être suspendues pendant migration ; aucune hypothèse de séquence exhaustive |
0385 Transaction V1 |
Review | maxSupportedTransactionVersion et les versions transaction restent génériques ; ne pas caper KSP à v0 |
Sources SIMD :
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0118-partitioned-epoch-reward-distribution.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0291-commission-rate-in-basis-points.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0296-larger-transactions.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0298-bank-hash-in-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/pull/301
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0307-add-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0326-alpenglow.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0337-parent-ready-update-marker.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0384-alpenglow-migration.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md
Les limites WebSocket sont donc des bornes KSP configurables et finies, jamais une transposition en dur de l'ancienne limite transaction de 1232 octets.
5. Méthodes unstable
blockSubscribe
Conditions officielles :
--rpc-pubsub-enable-block-subscription
--enable-rpc-transaction-history
KSP : warning centralisé à la création, test fixture toujours disponible, smoke live non requis.
slotsUpdatesSubscribe
Le format est explicitement annoncé comme susceptible de changer. Variants actuels :
firstShredReceived slot,timestamp
completed slot,timestamp
createdBank slot,parent,timestamp
frozen slot,timestamp,stats
dead slot,timestamp,err
optimisticConfirmation slot,timestamp
root slot,timestamp
stats actuel : numTransactionEntries, numSuccessfulTransactions, numFailedTransactions, maxTransactionsPerEntry.
voteSubscribe
Condition officielle :
--rpc-pubsub-enable-vote-subscription
Les votes observés sont gossip/pre-consensus ; aucune garantie d'entrée dans le ledger. Transport les livre comme wire, sans interprétation métier.
6. Lifecycle compliance initiale
| Contrat | Décision pre.001 |
Gate cible |
|---|---|---|
| plusieurs sessions / même URL | création physique explicite ; aucun singleton/pool automatique | Done pre.004, canary final pre.012 |
| plusieurs subs / session | registry actor par session | pre.006 |
| ID public subscription | local KSP stable | pre.006 |
| ID serveur | éphémère interne et remappé | pre.006/pre.007 |
| session states | Disconnected/Connecting/Active/Reconnecting/Closing/Closed/Failed | Done pre.002 |
| subscription states | Requested/Active/Resubscribing/Cancelling/Closed/Failed | Done pre.002 |
| reconnect | physique uniquement, budget/backoff finis | pre.007 |
| resubscribe | policy Never ou ActiveSubscriptions, ordre local déterministe |
pre.007 |
| continuity | gap observable, aucune promesse lossless | pre.007 |
| backpressure | queue par sub bounded ; overflow => fail local explicite | pre.008 |
| shutdown | explicite, bounded, annule reconnect et subscriptions | pre.005 |
| keepalive | pas de ping applicatif périodique sans besoin démontré | pre.005 |
7. Threat/security compliance initiale
| Invariant | Preuve attendue | Statut |
|---|---|---|
URL/credentials absents de Debug |
unit tests URL wrapper | Done pre.002 |
| URL/credentials absents des erreurs | validation URL + connection errors safe | Done through pre.004 |
| URL/credentials absents des logs | actor logs only safe endpoint metadata | Partial pre.004, capture finale future |
| snapshots sans URL/raw payload | unit shape + public contract | Done pre.002 |
| frame/message finis | settings bornés + WebSocketConfig raccordé |
Partial pre.004, adversarial pre.005 |
| JSON borné indirectement par message | oversized + malformed fixture | Planned |
| queues notifications bornées | capacité settings, channel futur | Partial pre.002 |
| pending RPC borné + timeout | map actor bornée + timeout request | Done pre.004 |
| reconnect loop bornée | repeated disconnect fixture | Planned |
| unsubscribe pendant reconnect ne resubscribe pas | race fixture | Planned |
| signature terminale ne resubscribe pas | terminal fixture | Planned |
| shutdown ne bloque pas | peer hostile/no close ack fixture | Planned |
| no Store/Program/Wallet/Config dep dans Transport | cargo tree + source canary | Planned |
no direct tracing dans Transport |
workspace audit + source audit | Done pre.002 |
8. Dependency compliance initiale
Candidate retenue au gate :
tokio-tungstenite 0.30.0
futures-util 0.3.34
Features prévues :
tokio-tungstenite: default-features=false + connect + rustls-tls-webpki-roots
futures-util: default-features=false + std + sink
Sources :
https://docs.rs/crate/tokio-tungstenite/0.30.0
https://docs.rs/crate/tokio-tungstenite/0.30.0/features
https://docs.rs/crate/futures-util/0.3.34
Alternatives auditées mais non retenues : tokio-websockets 0.13.3, fastwebsockets 0.10.0.
pre.004 matérialise tokio-tungstenite et futures-util dans [workspace.dependencies] sans features consumer au root. Transport active seulement connect, rustls-tls-webpki-roots, std et sink; tokio/net est ajouté côté dev fixture local.
9. Config compliance initiale
V1 hérité : HTTP-only strict. Contrat de compatibilité :
V1 -> support de lecture conservé, WS vide
V2 -> HTTP existant + ws_defaults + profiles[].ws_endpoints
profiles[].ws_endpoints[].kind -> discriminateur de famille/protocole WS
0.2.7 -> seule valeur supportée : solana_standard
Config -> WsTransportSettings
Transport -X-> Config
Le type Transport correspondant au discriminateur est prévu #[non_exhaustive]. L'objectif est de pouvoir ajouter ultérieurement une famille telle que Helius Enhanced WebSocket sans créer un nouveau conteneur Config ni injecter des options provider-specific dans les settings Solana standard. Toute valeur inconnue reste explicitement rejetée en 0.2.7; aucun paramètre Helius n'est implémenté par anticipation.
pre.003 remplace le schema enregistré par urn:ksp:schema:std.transport:v2, avec deux branches strictes : V1 HTTP-only et V2 HTTP + WebSocket. La compatibilité V1 ne relâche donc ni additionalProperties, ni la shape historique.
Preuves pre.003 :
config/std.transport.json format_version = 2
config/examples/std.transport.example.json format_version = 2
V1 fixture dédiée load + HTTP mapping OK attendu
V1 ws_settings = None
V2 ws_settings = Some(validated)
ws_defaults globals
profiles[].ws_endpoints profile-local
ws_endpoints[].kind enum schema solana_standard
ws_endpoints[].session overrides génériques optionnels
Config -> Transport seule direction de dépendance
ResolvedTransportConfig::settings() reste l'accesseur HTTP historique. http_settings() l'explicite et ws_settings() expose Option<&WsTransportSettings> afin que le V1 backward ne force jamais un faux WsTransportSettings vide. into_transport_settings() permet de consommer les deux contrats ensemble.
9.1 Checkpoint settings/lifecycle pre.002
Surface publique matérialisée :
WsEndpointUrl
WsProviderName
WsClusterName
WsProtocolKind::SolanaStandard
WsReconnectSettings
WsResubscribePolicy::{Never, ActiveSubscriptions}
WsSessionSettings
WsEndpointSettings
WsTransportSettings
WsSessionId
WsSubscriptionId
WsSessionState
WsSubscriptionState
WsSubscriptionKind
WsSessionSnapshot
WsSubscriptionSnapshot
Gates déterministes ajoutés :
ws:// et wss:// acceptés
HTTP rejeté par WsEndpointUrl
Debug URL redacted
erreur de scheme sans URL/credential
settings session default bornés
zero runtime bound rejeté
reconnect backoff inversé rejeté
endpoint names uniques
au moins un endpoint enabled
Debug WsTransportSettings sans URL/credential
9 familles standard WsSubscriptionKind
snapshots sans URL ni remote subscription id
public API canary crate-root
Logging : TRACING_TARGET reste défini dans constants.rs; les nouveaux événements utilisent exclusivement ksp_logging_lib::trace!, debug! et warn! avec des fields sûrs. Aucun tracing direct n'est ajouté.
Les defaults de taille/queue sont des policies KSP locales, pas des limites Solana. Leur enforcement réel et leurs tests oversized/slow-consumer restent attendus dans les tranches socket/backpressure.
9.2 Checkpoint runtime physique pre.004
Surface matérialisée :
WsSession::connect(endpoint) public, une connexion physique par appel
actor socket propriétaire exclusif du WebSocket
command queue tokio mpsc bounded
pending JSON-RPC BTreeMap bounded par max_pending_requests
request timeout command_timeout
response dispatch par id numérique KSP
state snapshot watch + WsSessionSnapshot
raw JSON-RPC public non, primitive pub(crate)
reconnect/resubscribe non, gates futurs
Fixtures déterministes pre.004 :
handshake local + JSON-RPC round-trip
deux sessions physiques distinctes sur la même URL
deux requests concurrentes + réponses inversées
erreur RPC applicative sans teardown de session
erreur de connexion sans URL/credential dans Error Debug
Les limites max_message_size, max_frame_size et max_write_buffer_size sont raccordées à tungstenite::WebSocketConfig. Les fixtures oversized et le shutdown/control-frame hostile restent explicitement pre.005.
10. Validation du gate pre.001
Exécuté dans le sandbox :
archive stable v0.2.6 vérifiée OK
lecture règles/architecture/plans/validation OK
inventaire Transport/Config réel OK
archive bot3 WebSocket auditée OK
audit docs officielles WebSocket OK
cross-check Git Agave v4.2.1 OK
audit SIMD WebSocket ciblé OK
inventaire exact 18 = 9+9 OK
audit dependencies candidates OK
state machines / reconnect / resubscribe DECIDED
backpressure / cancellation / shutdown / secrets DECIDED
shape Config V2 + discriminateur famille WS DECIDED
plan/sizing OK
python3 scripts/audit_rust_workspace_rules.py baseline OK
Tenté mais non exécutable dans le sandbox :
cargo fmt --all cargo absent
cargo check --workspace cargo absent
cargo clippy --workspace --all-targets cargo absent
La matrice ne considère donc pas pre.001 techniquement validé par Cargo tant que l'opérateur n'a pas exécuté ces gates sur son checkout.
11. Critères finaux à transformer en preuves
Avant rel.001, cette matrice doit obtenir :
18/18 méthodes official-index accounted
9/9 subscribe wrappers public typed
9/9 unsubscribe couverts par handles/registry
0 option officielle perdue
0 fuite URL/credential
N sessions same URL prouvé
N subscriptions same session prouvé
reconnect/resubscribe/backpressure/shutdown gates verts
unstable warnings centralisés
HTTP 52+14 non régressé
Config V1 backward + V2 WS validés (**Done `pre.003`**, compilation opérateur requise)
smoke live opt-in documenté
cargo tree inspecté
cargo test --workspace vert
README/USAGE synchronisés
prompt 0.2.8 préparé