# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode > **Statut courant : `0.2.9-pre.010-fix.001` est fermée par gate opérateur sans warning. `0.2.9-pre.011` est la tranche active : Config Transport V3, séparation explicite protocol/provider, mapping Config -> Yellowstone gRPC et premier profil PublicNode Mainnet. Le présent document a été réorganisé pendant `pre.011` : les décisions actives restent ici ; les journaux historiques détaillés restent dans `deltas/0.2.9/`.** ## 1. Objet et autorité de la release Base stable d'ouverture : ```text v0.2.8 ``` Release : ```text 0.2.9 — Yellowstone gRPC standard/provider-neutral ``` Le gate `pre.001`, ses fixes, les deltas techniques suivants et le code réel ont affiné le prompt de démarrage. L'ordre d'autorité utilisé pendant la release est : ```text règles normatives KSP code et fichiers réellement livrés nouveaux deltas immuables de 0.2.9 décisions courantes consolidées dans ce plan et la validation 012 prompt de démarrage pour les contraintes qui n'ont pas été explicitement supersédées ``` Les deltas historiques ne sont jamais réécrits pour refléter une décision ultérieure. ## 2. Résultat final attendu `0.2.9` doit fermer une première fondation Yellowstone gRPC exploitable sans devenir un SDK fournisseur : ```text backend gRPC distinct de HTTP et WebSocket moteur Tonic/Protobuf privé dans ksp-onchain-transport-lib façade Yellowstone standard provider-neutral 7 unary standard retenus Subscribe standard avec les familles/accounts/slots/transactions/blocks retenues 9 variantes SubscribeUpdate courantes stream bidirectionnel borné backpressure, half-close et shutdown déterministes reconnect KSP-owned et replay/from_slot prudent aucune promesse exactly-once/lossless non prouvée Config Transport V3 backward-readable V1/V2 provider et protocol distincts dans Config première intégration PublicNode strictement standard smoke live opt-in sans credential versionné non-régressions HTTP, WebSocket standard et Helius WebSocket ``` ## 3. Scope fermé par `pre.001` ### 3.1 Inclus ```text N1 — moteur Yellowstone gRPC N2 — standard Solana Yellowstone N3 — première intégration PublicNode quand elle réutilise le standard sans divergence wire TLS et metadata provider-neutral 7 unary standards retenus Subscribe standard retenu reconnect/replay observables mais non lossless Config V3 si le mapping reste Config -> Transport smokes PublicNode architecture-safe ``` ### 3.2 Hors scope `0.2.9` ```text SubscribeDeshred / pré-exécution extensions Triton spécifiques adapter Helius LaserStream gRPC spécifique adapter OrbitFlare spécifique pool/scheduler automatique complexe de sessions gRPC serveur Geyser/plugin validator Store/persistence/backfill historique workers/jobs d'acquisition replay lossless garanti refonte HTTP ou WebSocket ``` Les intégrations provider futures ne dupliquent jamais le moteur N1. Une façade provider n'existe que si elle porte une divergence réelle : auth, capabilities, restriction, extension wire ou policy lifecycle. ## 4. Audit upstream et dépendances retenues ### 4.1 Snapshot normatif du gate Le gate d'ouverture a réaudité Yellowstone courant et a constaté des versions indépendantes entre plugin, client et proto : ```text release GitHub observée v15.1.2+solana.4.2.0 — 2026-08-18 yellowstone-grpc-client 13.3.0 yellowstone-grpc-proto 12.6.0 prost/prost-types 0.14.x tonic 0.14.x ``` Les sources primaires restent : ```text https://github.com/rpcpool/yellowstone-grpc/releases https://github.com/rpcpool/yellowstone-grpc/blob/master/CHANGELOG.md https://github.com/rpcpool/yellowstone-grpc/blob/master/yellowstone-grpc-proto/proto/geyser.proto https://github.com/rpcpool/yellowstone-grpc/blob/master/yellowstone-grpc-proto/proto/solana-storage.proto https://github.com/rpcpool/yellowstone-grpc/blob/master/LICENSING.md https://docs.rs/crate/yellowstone-grpc-proto/latest https://docs.rs/crate/yellowstone-grpc-client/latest ``` Une réaudite finale est requise en `pre.012` si l'upstream a changé matériellement pendant la release. ### 4.2 Licence Le repository upstream est globalement `AGPL-3.0-only`, mais `LICENSING.md` affecte explicitement Apache-2.0 à : ```text examples/ yellowstone-grpc-client/ yellowstone-grpc-client-nodejs/ yellowstone-grpc-proto/ ``` Décision : ```text dépendance publiée yellowstone-grpc-proto = acceptée .proto vendored dans KSP = non source provenant des zones AGPL = non copiée future copie upstream = nouveau gate provenance/licence obligatoire ``` ### 4.3 Stratégie client | Stratégie | Décision | Raison principale | |-------------------------------------------------------|--------------------|-----------------------------------------------------------------------| | `yellowstone-grpc-client + yellowstone-grpc-proto` | non retenue | importerait trop de lifecycle/reconnect upstream et de surface client | | `yellowstone-grpc-proto + client KSP autour de tonic` | **retenue** | wire officiel, moteur/lifecycle/redaction KSP-owned | | proto/génération KSP vendored | fallback seulement | dette licence/synchronisation/build plus forte | Matérialisation courante : ```text yellowstone-grpc-proto ^12.6 runtime sans feature tonic yellowstone-grpc-proto dev/test avec feature tonic pour GeyserServer fixture tonic ^0.14 channel + TLS runtime ; codegen/server dev/test tonic-prost ^0.14 ProstCodec bas niveau http ^1.5 PathAndQuery interne yellowstone-grpc-client absent prost/prost-types aucune dépendance KSP directe proto vendored absent ``` Les graphes Cargo inspectés pendant `pre.002/pre.003` n'ont pas révélé de seconde génération incompatible à corriger. Le graph final est réinspecté en `pre.012`. ## 5. Matrice protocolaire fermée ### 5.1 Service `Geyser` | RPC | Forme | Classification | Cible `0.2.9` | État | |-----------------------|-------------|------------------------------------------------------|---------------|----------------------------| | `Subscribe` | bidi stream | standard Yellowstone | oui | **DONE** `pre.009/pre.010` | | `SubscribeDeshred` | bidi stream | extension/pré-exécution Triton publiée dans le proto | non | **OUT** | | `SubscribeReplayInfo` | unary | standard | oui | **DONE** | | `Ping` | unary | standard | oui | **DONE** | | `GetLatestBlockhash` | unary | standard | oui | **DONE** | | `GetBlockHeight` | unary | standard | oui | **DONE** | | `GetSlot` | unary | standard | oui | **DONE** | | `IsBlockhashValid` | unary | standard | oui | **DONE** | | `GetVersion` | unary | standard | oui | **DONE** | `SubscribeDeshred` reste explicitement exclu même s'il existe dans le proto publié : sa présence wire n'en fait pas une capacité provider-neutral de la fondation KSP. ### 5.2 `SubscribeRequest` | Champ | Sémantique | État | |-----------------------|----------------------------------------|-------------------------------| | `accounts` | map nom -> filtre accounts | **DONE** | | `slots` | map nom -> filtre slots | **DONE** | | `transactions` | map nom -> filtre transactions | **DONE** | | `transactions_status` | même famille de filtre transaction | **DONE** | | `blocks` | map nom -> filtre blocks | **DONE** | | `blocks_meta` | map nom -> filtre marqueur vide | **DONE** | | `entry` | map nom -> filtre marqueur vide | **DONE** | | `commitment` | optional Processed/Confirmed/Finalized | **DONE** | | `accounts_data_slice` | repeated offset/length | **DONE** | | `ping` | optional request ping/id | **DONE** | | `from_slot` | optional u64 | **DONE**, sémantique prudente | Accounts : ```text account[] owner[] filters[] nonempty_txn_signature? cuckoo_accounts_filter? memcmp { offset, oneof bytes | base58 | base64 } datasize token_account_state lamports { oneof eq | ne | lt | gt } ``` Slots : ```text filter_by_commitment? interslot_updates? statuses = processed | confirmed | finalized | first_shred_received | completed | created_bank | dead ``` Transactions et `transaction_status` : ```text vote? failed? signature? account_include[] account_exclude[] account_required[] cuckoo_account_include? token_accounts? = ALL | BALANCE_CHANGED ``` Blocks : ```text account_include[] include_transactions? include_accounts? include_entries? cuckoo_account_include? ``` `blocks_meta` et `entry` conservent la distinction absence / map vide / filtre nommé vide. Bornes KSP communes matérialisées : ```text filter groups nommés total <= 1024 filter name 1..128 octets, trim exact, sans contrôle filter names uniques globalement entre les sept maps accounts_data_slice count <= 128 accounts_data_slice length <= 64 MiB offset + length sans overflow u64 ``` Les bounds spécifiques Accounts/Transactions/Blocks sont ceux désormais testés dans leurs tranches respectives ; ils ne sont pas dupliqués comme knobs Config. ### 5.3 `SubscribeUpdate` | Variante | Champs structurants conservés | État | |----------------------|-----------------------------------------------------------------------------|----------| | `account` | account info + slot + `is_startup` | **DONE** | | `slot` | slot + parent? + status + dead_error? | **DONE** | | `transaction` | signature/is_vote/transaction/meta/index + slot | **DONE** | | `transaction_status` | slot/signature/is_vote/index/error | **DONE** | | `block` | slot/hash/rewards/time/height/parent/counts + transactions/accounts/entries | **DONE** | | `ping` | marker server ping | **DONE** | | `pong` | id | **DONE** | | `block_meta` | block metadata/counts sans tableaux complets | **DONE** | | `entry` | slot/index/num_hashes/hash/transaction counts/index | **DONE** | Le top-level conserve également `filters[]` et `created_at`. Les types Prost/Yellowstone générés restent privés. ### 5.4 Unary standards | RPC | Request | Response KSP utile | État | |-----------------------|---------------------------|------------------------------------------|----------| | `SubscribeReplayInfo` | vide | `first_available?` | **DONE** | | `Ping` | `count` | `count` | **DONE** | | `GetLatestBlockhash` | `commitment?` | slot, blockhash, last_valid_block_height | **DONE** | | `GetBlockHeight` | `commitment?` | block_height | **DONE** | | `GetSlot` | `commitment?` | slot | **DONE** | | `IsBlockhashValid` | blockhash + `commitment?` | slot + valid | **DONE** | | `GetVersion` | vide | version bornée | **DONE** | Ces capacités ne remplacent pas les wrappers Solana JSON-RPC HTTP. ## 6. Architecture runtime actuelle ### 6.1 Séparation des backends ```text HTTP HttpTransportSettings / pool HTTP WebSocket engine WsSession actor partagé Solana standard WS SolanaStandardWsSession Helius LaserStream WS HeliusLaserStreamWsSession Yellowstone gRPC engine YellowstoneGrpcChannel + moteur bidi KSP Solana Yellowstone standard contrats typed KSP provider descripteur d'exécution/capability, pas nouveau protocole ``` Interdictions : ```text pas de WsProtocolKind pour gRPC pas de WsEndpointSettings pour gRPC pas de client Tonic brut réexporté pas de second moteur physique par provider pas de façade provider vide qui ne ferait que renommer le standard ``` ### 6.2 Contrats publics principaux matérialisés ```text YellowstoneGrpcEndpointUrl YellowstoneGrpcProviderName YellowstoneGrpcClusterName YellowstoneGrpcMetadataEntry YellowstoneGrpcReconnectSettings YellowstoneGrpcSessionSettings YellowstoneGrpcEndpointSettings YellowstoneGrpcTransportSettings YellowstoneGrpcChannel YellowstoneSubscribeRequest + filtres typed YellowstoneSubscribeUpdate + variantes typed SolanaYellowstoneGrpcSubscribeSession YellowstoneGrpcSubscribeSnapshot 7 unary typed ``` Le wire Tonic/Prost reste privé et n'est pas une escape hatch publique. ### 6.3 Credentials et diagnostics Transport reçoit des valeurs déjà résolues par son consumer. Il ne connaît : ```text aucun KSP_SECRET_* aucun KSP_PUBLIC_* aucun std::env aucun header commercial hardcodé dans le standard ``` Les URLs, metadata sensibles, messages/details de `tonic::Status` et payloads arbitraires ne sont pas recopiés dans `Debug`, `Display`, snapshots ou contexts KSP. ## 7. Lifecycle, backpressure et continuité ### 7.1 Stream bidi Acquis depuis `pre.009` : ```text une request mpsc bornée consommée par Tonic une update queue bornée côté KSP mutation du SubscribeRequest sur le même stream Ping serveur -> réponse automatique appropriée Pong décodé server half-close observable client explicit close borné Drop best-effort sans panic oversized inbound/outbound borné slow receiver overflow terminal et observable shutdown déterministe ``` Aucune queue non bornée et aucun drop silencieux n'est présenté comme lossless. ### 7.2 Reconnect/replay Acquis depuis `pre.010` : ```text reconnect automatique oui, borné et KSP-owned resubscribe dernier SubscribeRequest complet accepté from_slot de reprise max(from_slot explicite, dernier slot observé) quand applicable SubscribeReplayInfo informatif first_available clamp/prouve un gap seulement s'il dépasse le slot demandé exactly-once non garanti lossless non garanti ordre global sans gap non garanti duplicate possible, compté, non supprimé silencieusement gap compté seulement lorsqu'une preuve est disponible shutdown during backoff interrompt la reconnexion budget reconnect épuisé état terminal safe ``` Snapshot public safe : ```text reconnect_count replay_attempt_count continuity_gap_count duplicate_update_count last_requested_from_slot last_observed_slot terminal state/error code safe ``` La présence de `from_slot` ou `SubscribeReplayInfo` n'autorise aucune promesse de replay historique complet. ## 8. Config Transport V3 — tranche `pre.011` ### 8.1 Compatibilité documentaire Décision fermée : ```text V1 = HTTP-only, backward-readable V2 = HTTP + WebSocket, backward-readable V3 = HTTP + WebSocket + Yellowstone gRPC optionnel par profil ``` Le schema V3 conserve des branches strictes V1/V2 au lieu de relâcher leurs `additionalProperties`. Shape V3 : ```text format_version = 3 retry ws_defaults grpc_defaults default_profile profiles[] { profile_id endpoints[] ws_endpoints[] grpc_endpoints[]? # optionnel par profil } ``` L'absence de `grpc_endpoints` dans un profil V3 signifie `None`, pas un `YellowstoneGrpcTransportSettings` vide inventé. ### 8.2 `grpc_defaults` Les defaults Config correspondent uniquement à de vrais settings runtime Transport : ```text connect_timeout_ms unary_timeout_ms close_timeout_ms reconnect.max_retries reconnect.initial_backoff_ms reconnect.max_backoff_ms request_channel_capacity update_channel_capacity max_inbound_message_size_bytes max_outbound_message_size_bytes ``` Les bounds de filtres Subscribe restent un contrat Transport fixe et ne deviennent pas des options Config sans besoin démontré. ### 8.3 Endpoint gRPC Chaque `grpc_endpoints[]` porte : ```text name enabled provider cluster protocol = solana_yellowstone url metadata[]? # classe publique secret_metadata[]? # classe secrète session? # overrides bornés ``` Axes distincts : ```text protocol = contrat wire standard, actuellement solana_yellowstone provider = environnement d'exécution descriptif, par exemple publicnode ``` `provider = publicnode` ne crée donc pas un `PublicNodeGrpcProtocol` ni une façade provider sans divergence réelle. ### 8.4 Provenance des metadata Config est propriétaire de la résolution : ```text metadata -> interdit toute provenance KSP_SECRET_*/KSPB_SECRET_* secret_metadata -> exige au moins une provenance secret et interdit une variable non-secret littéraux autour d'un placeholder secret -> autorisés ; safe_value masque seulement le segment secret ``` Transport reçoit ensuite `YellowstoneGrpcMetadataEntry` public/secret et ne connaît jamais le nom de variable d'environnement. ### 8.5 API Config sans rupture V2 L'API existante reste : ```text http_settings() ws_settings() into_transport_settings() -> (HTTP, Option) ``` `pre.011` ajoute : ```text grpc_settings() into_all_transport_settings() -> (HTTP, Option, Option) ``` Le tuple historique n'est pas modifié silencieusement. ## 9. PublicNode dans `0.2.9` ### 9.1 Mainnet La surface publique réauditée le 2026-08-24 confirme Yellowstone gRPC Solana Mainnet et affiche le host/port : ```text solana-yellowstone-grpc.publicnode.com:443 ``` Le wrapper KSP attend une URL `http/https`; Config matérialise donc le même endpoint TLS sous la forme `https://solana-yellowstone-grpc.publicnode.com:443`. `pre.011` versionne donc un profil : ```text profile_id = publicnode_mainnet provider = publicnode cluster = mainnet-beta protocol = solana_yellowstone credential = aucun ``` La partie HTTP/WS de ce profil reste standard Solana ; seul l'endpoint gRPC est PublicNode. ### 9.2 Testnet PublicNode affiche toujours une offre Solana Testnet gRPC, mais le hostname exact n'est pas exposé de façon suffisamment autoritative dans la surface publique inspectable pendant `pre.011`. Décision : ```text existence Testnet gRPC confirmée hostname Testnet exact non inventé profil Testnet committé non en pre.011 réaudit endpoint/live pre.012 ``` Un échec à confirmer le hostname Testnet ne bloque pas la fondation Mainnet ; il doit être documenté explicitement au gate final. ## 10. Threat model et bornes Menaces couvertes : ```text credential dans URI/metadata Status/message/details provider arbitraires Debug dérivé de filtres/payloads TLS/connect error qui réémet l'URI message inbound/outbound hostile stream flood / slow consumer filter explosion / collision de noms unknown enum/oneof server/client half-close reconnect loop node divergent après reconnect duplicate/gap après replay mutation tardive du stream ``` Réponses : ```text wrappers redacted allowlist de contexts KSP validation/bounds avant I/O queues bornées états terminaux observables reconnect budget borné aucune promesse de continuité non prouvée Config sensitivity gate avant construction de metadata secret ``` ## 11. Smoke ownership Ordre de preuve : ```text Transport programmatic -> PublicNode Mainnet Yellowstone Transport programmatic -> PublicNode Testnet seulement si endpoint exact confirmé Config V3 -> Transport -> PublicNode via composition légitime, jamais un smoke réseau placé dans Config par facilité ``` Un smoke Transport pur peut vivre dans `ksp-onchain-transport-lib/tests` puisqu'il construit ses settings programmatiquement. Un smoke cross-crates Config -> Transport ne doit pas devenir une responsabilité durable de `ksp-config-lib`. S'il n'existe pas encore de surface d'intégration appropriée, la procédure reste opérateur/documentée en `pre.012`. Aucun secret provider n'est versionné. ## 12. État des tranches et historique compact Les preuves détaillées restent dans les fichiers `deltas/0.2.9/*.md`. Le plan ne duplique plus leurs journaux complets. | Tranche | Objet | État consolidé | |-------------------|----------------------------------------------------------|----------------------| | `pre.001` + fixes | audit upstream, licence, providers, architecture, sizing | **CLOSED** | | `pre.002` + fixes | dépendances, settings/errors, channel minimal | **CLOSED** | | `pre.003` + fix | TLS, metadata, fixture locale, 7 unary | **CLOSED** | | `pre.004` + fix | Subscribe foundation/common | **CLOSED** | | `pre.005` + fix | Accounts + Slots | **CLOSED** | | `pre.006` | namespace privé HTTP explicite | **CLOSED** | | `pre.007` | Transactions + transaction_status | **CLOSED** | | `pre.008` + fix | Blocks + block_meta + entry | **CLOSED** | | `pre.009` + fix | bidi, Ping/Pong, backpressure, half-close, shutdown | **CLOSED** | | `pre.010` + fix | reconnect, from_slot, ReplayInfo, gaps/duplicates | **CLOSED** | | `pre.011` | Config V3 + protocol/provider + PublicNode Mainnet | **ACTIVE CANDIDATE** | | `pre.012` | live/compliance/docs/graph/prompt suivant | **PLANNED** | Gate opérateur de fermeture `pre.010-fix.001` : ```text fmt/audit/check/clippy PASS sans warning Transport unit 383/383 Transport public API 49/49 Transport completeness 43/43 Transport doctests 4/4 workspace dependencies 3/3 cargo test --workspace PASS ``` ## 13. Forecast restant ### `pre.011` — Config V3 + PublicNode Mainnet Cible : ```text workspace.package.version = 0.2.9-pre.11 schema std.transport V3 strict + branches V1/V2 Config adapter gRPC metadata publique/secrète + provenance protocol/provider distincts profil publicnode_mainnet compatibilité API V2 conservée plan 016 + validation 012 réorganisés ``` Preuves : ```text schema Draft 2020-12 valide fixtures V1/V2/V3 Config unit/public API/ownership Transport non-régressé workspace complet ``` ### `pre.012` — fermeture technique et live Cible : ```text réaudit upstream final réaudit PublicNode Testnet hostname smoke PublicNode Mainnet opt-in Testnet opt-in seulement si endpoint exact confirmé compliance HTTP 52 current + 14 historical compliance Standard WS 18/18 Helius WebSocket non régressé cargo tree direct + duplicates final README/USAGE Transport synchronisés matrice validation fermée prompt 0.2.10 préparé selon la séquence active workspace final vert ``` Si `pre.012` devient trop large, une `pre.013+` est créée ; le numéro n'est pas une deadline. ## 14. Critères de split Scinder avant dette silencieuse si : 1. une évolution upstream matérielle invalide le wire retenu ; 2. PublicNode exige une divergence provider-specific significative ; 3. le replay nécessite un sous-système de fork/equivocation plus large que la foundation ; 4. une tranche dépasse nettement le budget nominal sans frontière claire ; 5. le gate final montre une dette dépendance/licence ou une non-régression qui ne peut pas être corrigée proprement dans la tranche. Le noyau à préserver reste : ```text moteur Yellowstone + façade Solana standard + Config provider-neutral + première intégration PublicNode minimale ``` ## 15. Gates opérateur Après changement Rust : ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py cargo check --workspace cargo clippy --workspace --all-targets ``` Pour `pre.011` : ```bash cargo test -p ksp-config-lib cargo test -p ksp-config-lib --test public_api cargo test -p ksp-config-lib --test ownership cargo test -p ksp-onchain-transport-lib cargo test -p ksp-core-lib --test workspace_dependencies cargo test --workspace ``` Le graphe de dépendances gRPC n'est pas modifié par `pre.011`; son gate final complet reste en `pre.012` : ```bash cargo tree -p ksp-onchain-transport-lib cargo tree -p ksp-onchain-transport-lib --duplicates cargo tree --duplicates ``` ## 16. Conditions de clôture `0.2.9` ```text service/proto courant réconcilié SubscribeDeshred explicitement OUT 7 unary verts Subscribe standard et 9 updates verts backend gRPC distinct de HTTP/WS raw Tonic/Prost privé secrets/metadata redacted bounds/backpressure/shutdown verts reconnect/replay documentés sans lossless implicite Config V3 backward V1/V2 provider/protocol distincts PublicNode Mainnet validé Testnet validé si endpoint exact confirmé, sinon report factuel documenté HTTP 52+14 non régressé Standard WS 18/18 non régressé Helius WS non régressé cargo graphs finaux inspectés README/USAGE finaux synchronisés validation 012 fermée prompt release suivante prêt workspace final vert ``` ## 17. Séquence après `0.2.9` La séquence active a été recalibrée par les fixes de `pre.001`; cette décision est conservée pendant le nettoyage documentaire : ```text 0.2.9 moteur Yellowstone + Solana standard + PublicNode 0.2.10 OrbitFlare Yellowstone gRPC 0.2.11 Helius LaserStream gRPC 0.2.12 off-chain price transport 0.2.13 Price Desk + intégration prix Wallet Desk 0.2.14 interface/wire foundation 0.2.15 program-api foundation ``` Les intégrations suivantes restent dans le backlog non numéroté tant qu'aucune décision d'implémentation ne les fait entrer dans la séquence active : ```text TODO eRPC TODO Triton TODO Alchemy TODO QuickNode TODO Chainstack IDEAS Tatum IDEAS Shyft IDEAS Solinfra IDEAS NodeFlare ``` OrbitFlare reste le provider dédié `0.2.10` et Helius LaserStream gRPC `0.2.11` selon la séquence recalibrée par les fixes de `pre.001`. Chaque release doit réauditer auth, capabilities, restrictions, extensions wire, replay/from_slot et lifecycle au lieu de supposer une équivalence complète avec N2.