576 lines
40 KiB
Markdown
576 lines
40 KiB
Markdown
<!-- file: docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md -->
|
||
<!-- version: 11 -->
|
||
|
||
# Validation `0.2.7` — WebSocket Solana standard
|
||
|
||
> **Statut : matrice active, `0.2.7-pre.007`.** Settings/lifecycle `pre.002`, Config V2 `pre.003`, session physique `pre.004`, durcissement `pre.005`, registry `pre.006` et reconnect/resubscribe `pre.007` sont matérialisés. Backpressure per-sub et wrappers typed restent ouverts.
|
||
|
||
## 1. Baseline normative
|
||
|
||
Audit officiel effectué le **22 août 2026** contre :
|
||
|
||
```text
|
||
https://solana.com/docs/rpc/websocket
|
||
```
|
||
|
||
Compte exact de l'index courant :
|
||
|
||
```text
|
||
9 subscribe
|
||
9 unsubscribe
|
||
18 méthodes WebSocket totales
|
||
```
|
||
|
||
Répartition statut KSP :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
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é `RpcAccountInfoConfig` expose `min_context_slot`, mais le handler PubSub `v4.2.1` le destructure en `_ // ignored`. KSP ne le compte donc pas comme option WebSocket effective ;
|
||
- `programSubscribe` : `RpcProgramAccountsConfig.with_context` est 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 accepte `all`, `allWithVotes` ou `mentions` et rejette un filtre `mentions` contenant autre chose qu'exactement une adresse ;
|
||
- `signatureSubscribe` : le handler utilise la signature, `commitment` et `enable_received_notification`, sans option WebSocket supplémentaire ;
|
||
- `blockSubscribe` : le handler consomme le jeu d'options documenté et impose un commitment au moins `confirmed` ;
|
||
- `*Unsubscribe` : un ID serveur inconnu produit `InvalidParams` dans le handler audité ;
|
||
- `voteSubscribe` : `RpcVote.timestamp` est `Option<UnixTimestamp>`, donc optionnel au niveau wire ;
|
||
- `slotsUpdatesSubscribe` : `SlotUpdate` possède toujours les sept variantes courantes `FirstShredReceived`, `Completed`, `CreatedBank`, `Frozen`, `Dead`, `OptimisticConfirmation` et `Root`.
|
||
|
||
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 :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
--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 :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
--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 | **Done `pre.006`** |
|
||
| ID public subscription | local KSP stable | **Done `pre.006`** |
|
||
| ID serveur | éphémère interne et remappé | **Done `pre.006` puis `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 | **Done `pre.007`** |
|
||
| resubscribe | policy `Never` ou `ActiveSubscriptions`, ordre local déterministe | **Done `pre.007`** |
|
||
| continuity | gap observable, aucune promesse lossless | **Done `pre.007`** |
|
||
| backpressure | queue par sub bounded ; overflow => fail local explicite | **Done `pre.008`** |
|
||
| shutdown | explicite, bounded, annule reconnect et subscriptions | **Done `pre.005`** |
|
||
| keepalive | pas de ping applicatif périodique sans besoin démontré | **Done `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é | **Done `pre.005`** |
|
||
| JSON borné indirectement par message | oversized + malformed fixture | **Done `pre.005`** |
|
||
| queues notifications bornées | queue typed bornée + overflow isolé | **Done `pre.008`** |
|
||
| pending RPC borné + timeout | map actor bornée + timeout request | **Done `pre.004`** |
|
||
| reconnect loop bornée | repeated disconnect fixture | Done `pre.007` |
|
||
| unsubscribe pendant reconnect ne resubscribe pas | race fixture | Done `pre.007` |
|
||
| signature terminale ne resubscribe pas | terminal fixture | Planned |
|
||
| shutdown ne bloque pas | peer hostile/no close ack fixture | **Done `pre.005`** |
|
||
| 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 :
|
||
|
||
```text
|
||
tokio-tungstenite 0.30.0
|
||
futures-util 0.3.34
|
||
```
|
||
|
||
Features prévues :
|
||
|
||
```text
|
||
tokio-tungstenite: default-features=false + connect + rustls-tls-webpki-roots
|
||
futures-util: default-features=false + std + sink
|
||
```
|
||
|
||
Sources :
|
||
|
||
```text
|
||
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é :
|
||
|
||
```text
|
||
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` :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
WsEndpointUrl
|
||
WsProviderName
|
||
WsClusterName
|
||
WsProtocolKind::SolanaStandard
|
||
WsReconnectSettings
|
||
WsResubscribePolicy::{Never, ActiveSubscriptions}
|
||
WsSessionSettings
|
||
WsEndpointSettings
|
||
WsTransportSettings
|
||
WsSessionId
|
||
WsSubscriptionId
|
||
WsSessionState
|
||
WsSubscriptionState
|
||
WsSubscriptionKind
|
||
WsSessionSnapshot
|
||
WsSubscriptionSnapshot
|
||
```
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
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` :
|
||
|
||
```text
|
||
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`.
|
||
|
||
## 9.3 Checkpoint limits/control/shutdown `pre.005`
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
max_pending_requests saturé -> seule la request excédentaire est rejetée
|
||
outbound JSON-RPC > borne message/frame -> rejet avant socket write, session Active
|
||
inbound frame/message oversized -> rejet Tungstenite avant parse JSON ; depuis pre.007, reconnect borné
|
||
pending request silencieuse -> timeout + purge de capacité, session Active
|
||
Ping distant -> Pong automatique flushé, session Active
|
||
Close distant propre -> checkpoint `pre.005` Closed ; à partir de `pre.007`, perte physique reprise par reconnect borné
|
||
WsSession::close() -> Closing -> Closed
|
||
close() annule les pending requests avec ws_session_closed
|
||
peer hostile qui ne répond pas au Close -> shutdown borné
|
||
cycles connect/close répétés -> terminaison bornée
|
||
```
|
||
|
||
Le signal de shutdown est indépendant de la command queue et les opérations socket longues de l'actor surveillent ce signal. Aucun reconnect/resubscribe n'est activé par cette tranche.
|
||
|
||
## 9.4 Checkpoint registry subscriptions `pre.006`
|
||
|
||
Surface matérialisée :
|
||
|
||
```text
|
||
WsSubscription<T> handle public typed
|
||
WsSubscriptionId local stable, actor-assigned
|
||
remote subscription id interne/transient uniquement
|
||
registry local BTreeMap ordonnée par local ID
|
||
remote -> local mapping actor-owned
|
||
subscribe generic crate-private, wrappers publics futurs
|
||
unsubscribe handle public, bool Solana préservé
|
||
notification queue typed + bounded
|
||
reconnect/resubscribe non à ce checkpoint ; matérialisé en pre.007
|
||
backpressure adversarial complet oui, pre.008
|
||
```
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
subscribe ACK -> binding remote/local atomique
|
||
notification -> bonne subscription typed
|
||
2 familles -> IDs locaux 1 puis 2, remote IDs indépendants
|
||
unknown/stale remote ID -> safe drop, session Active
|
||
notification method mismatch -> subscription Failed seulement
|
||
typed decoder failure -> erreur livrée + subscription Failed seulement
|
||
unsubscribe -> exact *Unsubscribe avec remote ID interne
|
||
unsubscribe bool -> préservé au caller
|
||
snapshot -> local IDs + remote_bound uniquement
|
||
```
|
||
|
||
Le registry est détenu par le même actor que le socket et la pending map JSON-RPC. Aucun caller ne manipule le socket ni le remote ID. Les wrappers publics `accountSubscribe`, `programSubscribe`, etc. restent volontairement différés aux lots `pre.009+` afin de ne pas exposer une API raw provider-extension intermédiaire.
|
||
|
||
|
||
## 9.5 Checkpoint reconnect/resubscribe `pre.007`
|
||
|
||
Surface matérialisée :
|
||
|
||
```text
|
||
reconnect triggers EOF, Close distant inattendu, I/O/TLS/WebSocket/protocole structurel
|
||
reconnect budget fini, configurable
|
||
backoff exponentiel borné, sans jitter
|
||
shutdown pendant reconnect prioritaire, annule backoff/handshake
|
||
continuity_gap_count incrémenté une fois par perte de continuité logique
|
||
remote IDs après perte invalidés immédiatement
|
||
ActiveSubscriptions resubscribe local-ID croissant
|
||
Never aucune restauration automatique
|
||
budget reset seulement après retour complet à Active
|
||
unsubscribe pendant reconnect cancellation locale gagnante
|
||
ACK resubscribe tardif cleanup distant best-effort, aucune réactivation locale
|
||
backfill HTTP aucun
|
||
lossless guarantee aucune
|
||
```
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
remote Close + budget insuffisant -> Reconnecting puis Failed, jamais boucle infinie
|
||
2 subscriptions -> resubscribe dans l'ordre WsSubscriptionId 1 puis 2
|
||
remote IDs 101/202 -> remappés 301/302 sans changer les IDs locaux
|
||
params de subscribe -> conservés et rejoués à l'identique
|
||
unsubscribe pendant backoff -> sub Closed et absente de la sélection resubscribe
|
||
unsubscribe après émission resubscribe -> ACK tardif nettoyé par *Unsubscribe best-effort
|
||
policy Never -> session reconnectée Active, subscription terminale Failed
|
||
2 pertes séparées avec max_retries=1 -> deux recoveries possibles, budget réinitialisé après Active
|
||
continuity_gap_count -> 1 puis 2 sur deux pertes distinctes
|
||
```
|
||
|
||
Les requests applicatives en vol au moment d'une perte physique échouent ; Transport ne les rejoue pas implicitement. La restauration ne promet aucune continuité lossless et n'effectue aucun backfill HTTP. Les remote subscription IDs restent strictement internes et sont remplacés à chaque ACK de resubscribe.
|
||
|
||
## 9.6 Checkpoint backpressure/lifecycle `pre.008`
|
||
|
||
Surface matérialisée :
|
||
|
||
```text
|
||
notification_queue_capacity borne effective par WsSubscription<T>
|
||
overflow_count compteur session saturating, réellement incrémenté
|
||
terminal_error_code ErrorCode KSP sûr sur le handle Failed
|
||
queue pleine fail local uniquement, aucun drop silencieux
|
||
remote cleanup *Unsubscribe best-effort après overflow/receiver drop/erreur locale
|
||
max_active_subscriptions admission bornée, capacité réutilisable après terminaison
|
||
receiver abandonné détecté sur notification suivante, registry libéré
|
||
reconnect après overflow subscription Failed absente de toute restauration future
|
||
```
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
queue capacité 1 + 2 notifications -> overflow_count 1 + ws_backpressure_overflow
|
||
subscription lente en overflow -> Failed, première notification déjà queueée reste lisible puis channel fermé
|
||
subscription saine parallèle -> notification livrée et état Active
|
||
session après overflow isolé -> Active
|
||
binding distant overflow -> *Unsubscribe best-effort exact
|
||
max_active_subscriptions atteint -> création excédentaire rejetée sans incrémenter overflow_count
|
||
unsubscribe terminal -> capacité locale libérée puis nouvelle subscription acceptée
|
||
receiver droppé -> notification suivante déclenche cleanup distant et libère la capacité
|
||
method mismatch -> ws_protocol_error terminal sur le handle, session Active
|
||
decode typed invalide -> code invalid_response terminal sur le handle, session Active
|
||
policy Never après perte physique -> ws_connection_failed terminal sur le handle
|
||
resubscribe RPC application error -> rpc_application_error terminal sur le handle
|
||
```
|
||
|
||
`overflow_count` et `continuity_gap_count` sont deux signaux distincts : le premier mesure les saturations locales de consumers, le second les ruptures de continuité physique. Aucun des deux ne déclenche de replay ou de backfill automatique. Les causes terminales n'exposent qu'un `ErrorCode` KSP, jamais le payload distant, le remote subscription ID ou l'URL.
|
||
|
||
## 9.7 Checkpoint wrappers stables lot A `pre.009`
|
||
|
||
Surface publique ajoutée :
|
||
|
||
```text
|
||
WsSession::account_subscribe
|
||
SolanaAccountSubscribeConfig
|
||
WsSubscription<SolanaRpcResponse<SolanaAccount>>
|
||
|
||
WsSession::program_subscribe
|
||
SolanaProgramSubscribeConfig
|
||
SolanaProgramNotification = bare keyed account | contextual keyed account
|
||
|
||
WsSession::logs_subscribe
|
||
SolanaLogsSubscribeFilter = All | AllWithVotes | Mentions(Pubkey)
|
||
SolanaLogsNotification = signature + err nullable + logs ordonnés
|
||
```
|
||
|
||
Gates déterministes ajoutés :
|
||
|
||
```text
|
||
accountSubscribe -> params exacts pubkey + encoding/dataSlice/commitment
|
||
accountSubscribe -> aucun minContextSlot promis ou sérialisable par le config WS
|
||
accountNotification -> SolanaRpcContext + SolanaAccount partagé
|
||
account handle unsubscribe -> accountUnsubscribe avec remote ID interne
|
||
programSubscribe -> filters + withContext préservés, sortResults absent
|
||
program filters -> >4 et raw memcmp >128 rejetés avant émission subscribe
|
||
programNotification -> forme bare acceptée
|
||
programNotification -> forme contextualisée acceptée
|
||
program handle unsubscribe -> programUnsubscribe exact
|
||
logs filter -> all/allWithVotes/mentions exactement une pubkey
|
||
logsNotification -> context + signature + err null/object + ordre logs préservés
|
||
logsNotification sans champ err requis -> invalid_response typed, session non concernée
|
||
logs handle unsubscribe -> logsUnsubscribe exact
|
||
API publique -> aucun subscribe(method, raw params) exposé
|
||
```
|
||
|
||
Les trois wrappers passent par le même moteur actor/registry acquis en `pre.006`–`pre.008`; les remote IDs ne deviennent donc pas publics et les paramètres typés initiaux restent les specs rejouées lors d'un resubscribe `ActiveSubscriptions`. Le lot B (`signature`, `slot`, `root`) reste explicitement différé à `pre.010`.
|
||
|
||
## 10. Validation du gate `pre.001`
|
||
|
||
Exécuté dans le sandbox :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
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 :
|
||
|
||
```text
|
||
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é
|
||
```
|