v0.2.9-pre.011
This commit is contained in:
@@ -6,7 +6,7 @@ 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.9-pre.10.fix.1"
|
version = "0.2.9-pre.11"
|
||||||
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,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "urn:ksp:schema:std.transport:v2",
|
"$id": "urn:ksp:schema:std.transport:v3",
|
||||||
"title": "KSP standard HTTP + WebSocket Transport configuration",
|
"title": "KSP standard HTTP + WebSocket + Yellowstone gRPC Transport configuration",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/$defs/documentV1"
|
"$ref": "#/$defs/documentV1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/$defs/documentV2"
|
"$ref": "#/$defs/documentV2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/documentV3"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
@@ -455,6 +458,275 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"grpcPositiveMs": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 300000
|
||||||
|
},
|
||||||
|
"grpcCapacity": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 65536
|
||||||
|
},
|
||||||
|
"grpcMessageSize": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 536870912
|
||||||
|
},
|
||||||
|
"grpcReconnect": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"max_retries",
|
||||||
|
"initial_backoff_ms",
|
||||||
|
"max_backoff_ms"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"max_retries": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 100
|
||||||
|
},
|
||||||
|
"initial_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"max_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpcReconnectOverride": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"max_retries": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 100
|
||||||
|
},
|
||||||
|
"initial_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"max_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpcSession": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"connect_timeout_ms",
|
||||||
|
"unary_timeout_ms",
|
||||||
|
"close_timeout_ms",
|
||||||
|
"reconnect",
|
||||||
|
"request_channel_capacity",
|
||||||
|
"update_channel_capacity",
|
||||||
|
"max_inbound_message_size_bytes",
|
||||||
|
"max_outbound_message_size_bytes"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"connect_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"unary_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"close_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"reconnect": {
|
||||||
|
"$ref": "#/$defs/grpcReconnect"
|
||||||
|
},
|
||||||
|
"request_channel_capacity": {
|
||||||
|
"$ref": "#/$defs/grpcCapacity"
|
||||||
|
},
|
||||||
|
"update_channel_capacity": {
|
||||||
|
"$ref": "#/$defs/grpcCapacity"
|
||||||
|
},
|
||||||
|
"max_inbound_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/grpcMessageSize"
|
||||||
|
},
|
||||||
|
"max_outbound_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/grpcMessageSize"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpcSessionOverride": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"connect_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"unary_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"close_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/grpcPositiveMs"
|
||||||
|
},
|
||||||
|
"reconnect": {
|
||||||
|
"$ref": "#/$defs/grpcReconnectOverride"
|
||||||
|
},
|
||||||
|
"request_channel_capacity": {
|
||||||
|
"$ref": "#/$defs/grpcCapacity"
|
||||||
|
},
|
||||||
|
"update_channel_capacity": {
|
||||||
|
"$ref": "#/$defs/grpcCapacity"
|
||||||
|
},
|
||||||
|
"max_inbound_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/grpcMessageSize"
|
||||||
|
},
|
||||||
|
"max_outbound_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/grpcMessageSize"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpcMetadata": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"key",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 128,
|
||||||
|
"pattern": "^[0-9a-z_.-]+$"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 8192
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpcEndpoint": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"provider",
|
||||||
|
"cluster",
|
||||||
|
"protocol",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"const": "solana_yellowstone"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 8192
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 64,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/grpcMetadata"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"secret_metadata": {
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 64,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/grpcMetadata"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"$ref": "#/$defs/grpcSessionOverride"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profileV3": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"profile_id",
|
||||||
|
"endpoints",
|
||||||
|
"ws_endpoints"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"profile_id": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/httpEndpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws_endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/wsEndpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"grpc_endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 128,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/grpcEndpoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"documentV3": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"format_version",
|
||||||
|
"retry",
|
||||||
|
"ws_defaults",
|
||||||
|
"grpc_defaults",
|
||||||
|
"default_profile",
|
||||||
|
"profiles"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"format_version": {
|
||||||
|
"const": 3
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"$ref": "#/$defs/retry"
|
||||||
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"$ref": "#/$defs/wsSession"
|
||||||
|
},
|
||||||
|
"grpc_defaults": {
|
||||||
|
"$ref": "#/$defs/grpcSession"
|
||||||
|
},
|
||||||
|
"default_profile": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/profileV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"format_version": 2,
|
"format_version": 3,
|
||||||
"retry": {
|
"retry": {
|
||||||
"max_retries": 2,
|
"max_retries": 2,
|
||||||
"initial_backoff_ms": 100,
|
"initial_backoff_ms": 100,
|
||||||
@@ -22,6 +22,20 @@
|
|||||||
"max_frame_size_bytes": 16777216,
|
"max_frame_size_bytes": 16777216,
|
||||||
"max_write_buffer_size_bytes": 1048576
|
"max_write_buffer_size_bytes": 1048576
|
||||||
},
|
},
|
||||||
|
"grpc_defaults": {
|
||||||
|
"connect_timeout_ms": 10000,
|
||||||
|
"unary_timeout_ms": 10000,
|
||||||
|
"close_timeout_ms": 5000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 5,
|
||||||
|
"initial_backoff_ms": 250,
|
||||||
|
"max_backoff_ms": 5000
|
||||||
|
},
|
||||||
|
"request_channel_capacity": 128,
|
||||||
|
"update_channel_capacity": 256,
|
||||||
|
"max_inbound_message_size_bytes": 67108864,
|
||||||
|
"max_outbound_message_size_bytes": 67108864
|
||||||
|
},
|
||||||
"default_profile": "devnet_public",
|
"default_profile": "devnet_public",
|
||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
@@ -105,6 +119,57 @@
|
|||||||
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "publicnode_mainnet",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_mainnet_public",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 15000,
|
||||||
|
"max_idle_connections_per_host": 8,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"priority": 100,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 10,
|
||||||
|
"max_concurrent_requests": 8,
|
||||||
|
"pause_after_rate_limit_ms": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_mainnet_public_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grpc_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "publicnode_solana_mainnet_yellowstone",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "publicnode",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"protocol": "solana_yellowstone",
|
||||||
|
"url": "https://solana-yellowstone-grpc.publicnode.com:443"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-config-lib/README.md -->
|
<!-- file: crates/ksp-config-lib/README.md -->
|
||||||
<!-- version: 8 -->
|
<!-- version: 9 -->
|
||||||
|
|
||||||
# ksp-config-lib
|
# ksp-config-lib
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ Un secret reste accessible au runtime ou au management lorsqu'un consumer autori
|
|||||||
|
|
||||||
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
||||||
|
|
||||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport les accepte pour les URLs HTTP et WebSocket : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport accepte les valeurs secrètes pour les URLs HTTP/WebSocket et, en V3, pour `grpc_endpoints[].secret_metadata[]` : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. Les metadata gRPC publiques et secrètes sont séparées et leur provenance Config est contrôlée avant mapping. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
|
|||||||
- [`TODO.md`](TODO.md) — points explicitement différés ;
|
- [`TODO.md`](TODO.md) — points explicitement différés ;
|
||||||
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
||||||
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
||||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard Transport V2 HTTP + WebSocket, avec lecture backward du V1 HTTP-only ;
|
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard Transport V3 HTTP + WebSocket + Yellowstone gRPC, avec lecture backward des V1/V2 ;
|
||||||
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
||||||
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
||||||
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||||
<!-- version: 11 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# Utilisation de ksp-config-lib
|
# Utilisation de ksp-config-lib
|
||||||
|
|
||||||
@@ -125,9 +125,9 @@ Un `logs_directory` relatif est ancré sur le current working directory du proce
|
|||||||
|
|
||||||
Les `files[].path` restent relatifs sous le root Logging, y compris après interpolation.
|
Les `files[].path` restent relatifs sous le root Logging, y compris après interpolation.
|
||||||
|
|
||||||
### 4.1 Construire le Transport HTTP + WebSocket depuis Config
|
### 4.1 Construire le Transport HTTP + WebSocket + Yellowstone gRPC depuis Config
|
||||||
|
|
||||||
Config possède également l'adapter du document `std.transport` vers le contrat runtime de `ksp-onchain-transport-lib` :
|
Config possède l'adapter du document `std.transport` vers les contrats runtime de `ksp-onchain-transport-lib` :
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let transport = match engine.load_resolved_transport_config(std::option::Option::None, &environment) {
|
let transport = match engine.load_resolved_transport_config(std::option::Option::None, &environment) {
|
||||||
@@ -137,18 +137,30 @@ let transport = match engine.load_resolved_transport_config(std::option::Option:
|
|||||||
|
|
||||||
let http_settings = transport.http_settings();
|
let http_settings = transport.http_settings();
|
||||||
let ws_settings = transport.ws_settings();
|
let ws_settings = transport.ws_settings();
|
||||||
let _ = (http_settings, ws_settings);
|
let grpc_settings = transport.grpc_settings();
|
||||||
|
let _ = (http_settings, ws_settings, grpc_settings);
|
||||||
```
|
```
|
||||||
|
|
||||||
`std.transport` V2 conserve `retry` et `profiles[].endpoints[]` pour HTTP, ajoute `ws_defaults` et `profiles[].ws_endpoints[]`, puis accepte les protocoles WebSocket `kind = "solana_standard"` et `kind = "helius_laserstream"`. Ce second discriminateur appartient exclusivement au namespace WebSocket et mappe vers `WsProtocolKind::HeliusLaserStream`; il ne préfigure aucun contrat LaserStream gRPC. Un `ws_endpoints[].session` optionnel surcharge seulement les paramètres génériques de `WsSessionSettings`.
|
`std.transport` V3 conserve intégralement les shapes V1/V2, ajoute `grpc_defaults` au niveau global et permet `profiles[].grpc_endpoints[]`. Les profils V3 peuvent rester HTTP + WebSocket seulement : l'absence de `grpc_endpoints` mappe vers `None` et n'invente aucun endpoint. La lecture V1 HTTP-only et V2 HTTP + WebSocket reste stricte et backward-compatible.
|
||||||
|
|
||||||
Pour Helius LaserStream WebSocket, l'exemple versionné couvre explicitement les deux réseaux supportés par ce contrat : mainnet via `wss://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me}` et devnet via `wss://devnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY:-replace-me}`. Les deux réseaux vivent dans des profils Config distincts afin de ne pas mélanger des clusters dans un même profil logique. Config reste l'unique propriétaire de `KSP_SECRET_HELIUS_API_KEY` : il résout la clé dans l'URL effective et transmet au Transport un `WsEndpointUrl` utilisable au runtime. Dans `safe_value`, Config conserve les segments littéraux non sensibles d'une chaîne composée et remplace uniquement chaque segment secret par `********` ; les projections deviennent donc respectivement `wss://mainnet.helius-rpc.com/?api-key=********` et `wss://devnet.helius-rpc.com/?api-key=********`. Les représentations `Debug` restent sûres et n'exposent jamais la clé réelle. Transport ne lit jamais directement l'environnement.
|
Un endpoint gRPC V3 sépare explicitement :
|
||||||
|
|
||||||
Le même schema enregistré conserve la lecture stricte du V1 historique : dans ce cas `http_settings()` reste disponible et `ws_settings()` retourne `None`. Aucun `WsTransportSettings` vide n'est inventé pour simuler l'absence de WebSocket.
|
```text
|
||||||
|
provider = description de l'opérateur/exécution, par exemple publicnode
|
||||||
|
protocol = solana_yellowstone
|
||||||
|
metadata = metadata non secrète
|
||||||
|
secret_metadata = metadata dont la valeur doit avoir une provenance KSP_SECRET_*/KSPB_SECRET_*
|
||||||
|
```
|
||||||
|
|
||||||
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. Les URLs HTTP et WebSocket peuvent provenir de `KSP_PUBLIC_*` ou de `KSP_SECRET_*`; dans ce dernier cas la valeur réelle reste disponible au runtime Transport, mais `ResolvedTransportConfig::effective().safe_value()` et les représentations `Debug` sont redacted.
|
`protocol` n'est pas un nouveau `WsProtocolKind` et ne transforme pas PublicNode en protocole. Config résout les placeholders, vérifie la classe de sensibilité des metadata puis construit `YellowstoneGrpcTransportSettings`. Transport ne lit jamais l'environnement. Les valeurs de `secret_metadata` sont disponibles au runtime mais redacted dans `safe_value` et dans les représentations `Debug`.
|
||||||
|
|
||||||
La dépendance reste unidirectionnelle : Config connaît les contrats Transport pour les construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
Le provider affiche `solana-yellowstone-grpc.publicnode.com:443` ; le profil versionné `publicnode_mainnet` le représente sous la forme URL TLS requise par Transport, `https://solana-yellowstone-grpc.publicnode.com:443`, sans credential. Aucun hostname Testnet n'est versionné tant que sa valeur exacte n'est pas confirmée depuis une source opérateur/officielle.
|
||||||
|
|
||||||
|
L'accesseur historique `into_transport_settings()` conserve volontairement son tuple `(HTTP, Option<WS>)`. Un consumer ayant besoin des trois backends utilise `into_all_transport_settings()` ou les accesseurs séparés afin de ne pas casser silencieusement les consumers V2.
|
||||||
|
|
||||||
|
Les protocoles WebSocket restent `kind = "solana_standard"` et `kind = "helius_laserstream"`. Helius LaserStream WebSocket conserve ses URLs Config-owned et sa clé `KSP_SECRET_HELIUS_API_KEY`; cette surface est indépendante de Yellowstone gRPC.
|
||||||
|
|
||||||
|
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. La dépendance reste unidirectionnelle : Config connaît les contrats Transport pour les construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
||||||
|
|
||||||
### 4.2 Résoudre le répertoire Wallet depuis Config
|
### 4.2 Résoudre le répertoire Wallet depuis Config
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/src/lib.rs
|
// file: crates/ksp-config-lib/src/lib.rs
|
||||||
// version: 17
|
// version: 18
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -9,8 +9,9 @@
|
|||||||
//!
|
//!
|
||||||
//! The `0.1.3` surface owns bootstrap roots, the logical file registry, JSON/JSON Schema validation, standard-document profiles, generic composites and
|
//! The `0.1.3` surface owns bootstrap roots, the logical file registry, JSON/JSON Schema validation, standard-document profiles, generic composites and
|
||||||
//! KSP/KSPB environment resolution through process + `.env` + fallback precedence. Resolved values preserve real/safe representations, sensitivity and
|
//! KSP/KSPB environment resolution through process + `.env` + fallback precedence. Resolved values preserve real/safe representations, sensitivity and
|
||||||
//! provenance. Standard Logging, HTTP Transport and Wallet documents map explicitly to their runtime consumer contracts, while the management surface
|
//! provenance. Standard Logging, on-chain Transport (HTTP/WebSocket/Yellowstone gRPC) and Wallet documents map explicitly to their runtime consumer
|
||||||
//! provides typed Logging mutation, safe environment reports, explicit privileged reveal calls and atomic JSON/`.env` persistence.
|
//! contracts, while the management surface provides typed Logging mutation, safe environment reports, explicit privileged reveal calls and atomic
|
||||||
|
//! JSON/`.env` persistence.
|
||||||
|
|
||||||
mod bootstrap;
|
mod bootstrap;
|
||||||
mod composite;
|
mod composite;
|
||||||
@@ -154,9 +155,9 @@ pub use self::registry::DEFAULT_COMPOSITE_SCHEMA_FILENAME;
|
|||||||
pub use self::registry::DEFAULT_STD_LOGGING_FILENAME;
|
pub use self::registry::DEFAULT_STD_LOGGING_FILENAME;
|
||||||
/// Default physical filename for the standard Logging JSON Schema document.
|
/// Default physical filename for the standard Logging JSON Schema document.
|
||||||
pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
||||||
/// Default physical filename for the standard HTTP + WebSocket Transport configuration document.
|
/// Default physical filename for the standard HTTP + WebSocket + Yellowstone gRPC Transport configuration document.
|
||||||
pub use self::registry::DEFAULT_STD_TRANSPORT_FILENAME;
|
pub use self::registry::DEFAULT_STD_TRANSPORT_FILENAME;
|
||||||
/// Default physical filename for the standard HTTP + WebSocket Transport JSON Schema document.
|
/// Default physical filename for the standard HTTP + WebSocket + Yellowstone gRPC Transport JSON Schema document.
|
||||||
pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME;
|
pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME;
|
||||||
/// Default physical filename for the standard Wallet configuration document.
|
/// Default physical filename for the standard Wallet configuration document.
|
||||||
pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
|
pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
|
||||||
@@ -168,13 +169,13 @@ pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
|
|||||||
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
||||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
||||||
/// Logical file identifier for the standard HTTP + WebSocket Transport JSON Schema document.
|
/// Logical file identifier for the standard HTTP + WebSocket + Yellowstone gRPC Transport JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_TRANSPORT;
|
pub use self::registry::FILE_ID_SCHEMA_STD_TRANSPORT;
|
||||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_WALLET;
|
pub use self::registry::FILE_ID_SCHEMA_STD_WALLET;
|
||||||
/// Logical file identifier for the standard Logging configuration document.
|
/// Logical file identifier for the standard Logging configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_LOGGING;
|
pub use self::registry::FILE_ID_STD_LOGGING;
|
||||||
/// Logical file identifier for the standard HTTP + WebSocket Transport configuration document.
|
/// Logical file identifier for the standard HTTP + WebSocket + Yellowstone gRPC Transport configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_TRANSPORT;
|
pub use self::registry::FILE_ID_STD_TRANSPORT;
|
||||||
/// Logical file identifier for the standard Wallet configuration document.
|
/// Logical file identifier for the standard Wallet configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_WALLET;
|
pub use self::registry::FILE_ID_STD_WALLET;
|
||||||
@@ -188,7 +189,7 @@ pub use self::sensitivity::REDACTED_CONFIG_VALUE;
|
|||||||
pub use self::sensitivity::ResolvedConfigJson;
|
pub use self::sensitivity::ResolvedConfigJson;
|
||||||
/// One resolved Config string preserving real/safe representations and provenance.
|
/// One resolved Config string preserving real/safe representations and provenance.
|
||||||
pub use self::sensitivity::ResolvedConfigText;
|
pub use self::sensitivity::ResolvedConfigText;
|
||||||
/// Effective standard Transport configuration mapped to HTTP and optional WebSocket runtime settings.
|
/// Effective standard Transport configuration mapped to HTTP plus optional WebSocket and Yellowstone gRPC runtime settings.
|
||||||
pub use self::transport::ResolvedTransportConfig;
|
pub use self::transport::ResolvedTransportConfig;
|
||||||
/// Effective standard Wallet configuration resolved to validated filesystem roots.
|
/// Effective standard Wallet configuration resolved to validated filesystem roots.
|
||||||
pub use self::wallet::ResolvedWalletConfig;
|
pub use self::wallet::ResolvedWalletConfig;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/src/registry.rs
|
// file: crates/ksp-config-lib/src/registry.rs
|
||||||
// version: 9
|
// version: 10
|
||||||
|
|
||||||
/// Bootstrap argument used to replace a known Config filename mapping.
|
/// Bootstrap argument used to replace a known Config filename mapping.
|
||||||
pub const ARG_FILE_MAP: &str = "--filemap";
|
pub const ARG_FILE_MAP: &str = "--filemap";
|
||||||
@@ -11,9 +11,9 @@ pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
|||||||
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
||||||
/// Default physical filename for the standard Logging JSON Schema document.
|
/// Default physical filename for the standard Logging JSON Schema document.
|
||||||
pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
||||||
/// Default physical filename for the standard HTTP + WebSocket Transport configuration document.
|
/// Default physical filename for the standard HTTP + WebSocket + Yellowstone gRPC Transport configuration document.
|
||||||
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
||||||
/// Default physical filename for the standard HTTP + WebSocket Transport JSON Schema document.
|
/// Default physical filename for the standard HTTP + WebSocket + Yellowstone gRPC Transport JSON Schema document.
|
||||||
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
||||||
/// Default physical filename for the standard Wallet configuration document.
|
/// Default physical filename for the standard Wallet configuration document.
|
||||||
pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
|
pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
|
||||||
@@ -25,13 +25,13 @@ pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-w
|
|||||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||||
/// Logical file identifier for the standard HTTP + WebSocket Transport JSON Schema document.
|
/// Logical file identifier for the standard HTTP + WebSocket + Yellowstone gRPC Transport JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
||||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_WALLET: &str = "schema.std.wallet";
|
pub const FILE_ID_SCHEMA_STD_WALLET: &str = "schema.std.wallet";
|
||||||
/// Logical file identifier for the standard Logging configuration document.
|
/// Logical file identifier for the standard Logging configuration document.
|
||||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||||
/// Logical file identifier for the standard HTTP + WebSocket Transport configuration document.
|
/// Logical file identifier for the standard HTTP + WebSocket + Yellowstone gRPC Transport configuration document.
|
||||||
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
||||||
/// Logical file identifier for the standard Wallet configuration document.
|
/// Logical file identifier for the standard Wallet configuration document.
|
||||||
pub const FILE_ID_STD_WALLET: &str = "cfg.std.wallet";
|
pub const FILE_ID_STD_WALLET: &str = "cfg.std.wallet";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// file: crates/ksp-config-lib/src/transport.rs
|
// file: crates/ksp-config-lib/src/transport.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
/// Effective standard on-chain Transport configuration resolved from Config and mapped to HTTP and optional WebSocket runtime contracts.
|
/// Effective standard on-chain Transport configuration resolved from Config and mapped to HTTP plus optional WebSocket and Yellowstone gRPC runtime contracts.
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ResolvedTransportConfig {
|
pub struct ResolvedTransportConfig {
|
||||||
file_id: crate::ConfigFileId,
|
file_id: crate::ConfigFileId,
|
||||||
@@ -11,6 +11,7 @@ pub struct ResolvedTransportConfig {
|
|||||||
effective: crate::ResolvedConfigJson,
|
effective: crate::ResolvedConfigJson,
|
||||||
settings: ksp_onchain_transport_lib::HttpTransportSettings,
|
settings: ksp_onchain_transport_lib::HttpTransportSettings,
|
||||||
ws_settings: std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>,
|
ws_settings: std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>,
|
||||||
|
grpc_settings: std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcTransportSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedTransportConfig {
|
impl ResolvedTransportConfig {
|
||||||
@@ -61,7 +62,7 @@ impl ResolvedTransportConfig {
|
|||||||
return &self.settings;
|
return &self.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns validated WebSocket Transport settings when the selected document uses format V2.
|
/// Returns validated WebSocket Transport settings when the selected document uses format V2 or V3.
|
||||||
///
|
///
|
||||||
/// Backward-compatible V1 HTTP-only documents return [`std::option::Option::None`].
|
/// Backward-compatible V1 HTTP-only documents return [`std::option::Option::None`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -69,19 +70,42 @@ impl ResolvedTransportConfig {
|
|||||||
return self.ws_settings.as_ref();
|
return self.ws_settings.as_ref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns validated Yellowstone gRPC Transport settings when the selected V3 profile declares gRPC endpoints.
|
||||||
|
///
|
||||||
|
/// V1/V2 documents and V3 profiles without `grpc_endpoints` return [`std::option::Option::None`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn grpc_settings(&self) -> std::option::Option<&ksp_onchain_transport_lib::YellowstoneGrpcTransportSettings> {
|
||||||
|
return self.grpc_settings.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes this resolved Config and returns the mapped runtime HTTP Transport settings.
|
/// Consumes this resolved Config and returns the mapped runtime HTTP Transport settings.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_settings(self) -> ksp_onchain_transport_lib::HttpTransportSettings {
|
pub fn into_settings(self) -> ksp_onchain_transport_lib::HttpTransportSettings {
|
||||||
return self.settings;
|
return self.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes this resolved Config and returns both HTTP and optional WebSocket runtime settings.
|
/// Consumes this resolved Config and returns HTTP plus optional WebSocket runtime settings.
|
||||||
|
///
|
||||||
|
/// This compatibility accessor intentionally preserves the V2 tuple shape. Use [`Self::into_all_transport_settings`] when Yellowstone gRPC settings are
|
||||||
|
/// also required.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_transport_settings(
|
pub fn into_transport_settings(
|
||||||
self,
|
self,
|
||||||
) -> (ksp_onchain_transport_lib::HttpTransportSettings, std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>) {
|
) -> (ksp_onchain_transport_lib::HttpTransportSettings, std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>) {
|
||||||
return (self.settings, self.ws_settings);
|
return (self.settings, self.ws_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes this resolved Config and returns HTTP plus optional WebSocket and Yellowstone gRPC runtime settings.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_all_transport_settings(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
ksp_onchain_transport_lib::HttpTransportSettings,
|
||||||
|
std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>,
|
||||||
|
std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcTransportSettings>,
|
||||||
|
) {
|
||||||
|
return (self.settings, self.ws_settings, self.grpc_settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for ResolvedTransportConfig {
|
impl std::fmt::Debug for ResolvedTransportConfig {
|
||||||
@@ -94,15 +118,17 @@ impl std::fmt::Debug for ResolvedTransportConfig {
|
|||||||
.field("selection_source", &self.selection_source)
|
.field("selection_source", &self.selection_source)
|
||||||
.field("effective", &self.effective)
|
.field("effective", &self.effective)
|
||||||
.field("has_ws_settings", &self.ws_settings.is_some())
|
.field("has_ws_settings", &self.ws_settings.is_some())
|
||||||
|
.field("has_grpc_settings", &self.grpc_settings.is_some())
|
||||||
.finish_non_exhaustive();
|
.finish_non_exhaustive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::ConfigDocumentEngine {
|
impl crate::ConfigDocumentEngine {
|
||||||
/// Loads the standard Transport document, selects a profile, resolves environment placeholders and maps HTTP plus optional WebSocket runtime settings.
|
/// Loads the standard Transport document, selects a profile, resolves environment placeholders and maps HTTP plus optional WebSocket and Yellowstone
|
||||||
|
/// gRPC runtime settings.
|
||||||
///
|
///
|
||||||
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. Secret endpoint URLs are allowed
|
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. Secret endpoint URLs are allowed
|
||||||
/// because Transport URL wrappers own runtime redaction. V1 documents remain HTTP-only; V2 documents require WebSocket defaults and endpoints.
|
/// because Transport URL wrappers own runtime redaction. V1 remains HTTP-only, V2 adds WebSocket and V3 can additionally map Yellowstone gRPC.
|
||||||
pub fn load_resolved_transport_config(
|
pub fn load_resolved_transport_config(
|
||||||
&self,
|
&self,
|
||||||
requested_profile: std::option::Option<&str>,
|
requested_profile: std::option::Option<&str>,
|
||||||
@@ -121,7 +147,8 @@ impl crate::ConfigDocumentEngine {
|
|||||||
return resolve_transport_profile(&profile, environment);
|
return resolve_transport_profile(&profile, environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps an already resolved standard Transport profile to HTTP plus optional WebSocket runtime adapters while preserving selection provenance.
|
/// Maps an already resolved standard Transport profile to HTTP plus optional WebSocket and Yellowstone gRPC runtime adapters while preserving
|
||||||
|
/// selection provenance.
|
||||||
///
|
///
|
||||||
/// This entry point is intended for profiles selected by a composite. The profile must reference `cfg.std.transport`.
|
/// This entry point is intended for profiles selected by a composite. The profile must reference `cfg.std.transport`.
|
||||||
pub fn resolve_transport_config_profile(
|
pub fn resolve_transport_config_profile(
|
||||||
@@ -148,9 +175,13 @@ struct EffectiveTransportSource {
|
|||||||
retry: EffectiveRetrySource,
|
retry: EffectiveRetrySource,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
ws_defaults: std::option::Option<EffectiveWsSessionSource>,
|
ws_defaults: std::option::Option<EffectiveWsSessionSource>,
|
||||||
|
#[serde(default)]
|
||||||
|
grpc_defaults: std::option::Option<EffectiveGrpcSessionSource>,
|
||||||
endpoints: std::vec::Vec<EffectiveEndpointSource>,
|
endpoints: std::vec::Vec<EffectiveEndpointSource>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
ws_endpoints: std::option::Option<std::vec::Vec<EffectiveWsEndpointSource>>,
|
ws_endpoints: std::option::Option<std::vec::Vec<EffectiveWsEndpointSource>>,
|
||||||
|
#[serde(default)]
|
||||||
|
grpc_endpoints: std::option::Option<std::vec::Vec<EffectiveGrpcEndpointSource>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
@@ -255,6 +286,72 @@ struct EffectiveWsReconnectOverrideSource {
|
|||||||
max_backoff_ms: std::option::Option<u64>,
|
max_backoff_ms: std::option::Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcReconnectSource {
|
||||||
|
max_retries: u32,
|
||||||
|
initial_backoff_ms: u64,
|
||||||
|
max_backoff_ms: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcReconnectOverrideSource {
|
||||||
|
max_retries: std::option::Option<u32>,
|
||||||
|
initial_backoff_ms: std::option::Option<u64>,
|
||||||
|
max_backoff_ms: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcSessionSource {
|
||||||
|
connect_timeout_ms: u64,
|
||||||
|
unary_timeout_ms: u64,
|
||||||
|
close_timeout_ms: u64,
|
||||||
|
reconnect: EffectiveGrpcReconnectSource,
|
||||||
|
request_channel_capacity: usize,
|
||||||
|
update_channel_capacity: usize,
|
||||||
|
max_inbound_message_size_bytes: usize,
|
||||||
|
max_outbound_message_size_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcSessionOverrideSource {
|
||||||
|
connect_timeout_ms: std::option::Option<u64>,
|
||||||
|
unary_timeout_ms: std::option::Option<u64>,
|
||||||
|
close_timeout_ms: std::option::Option<u64>,
|
||||||
|
reconnect: std::option::Option<EffectiveGrpcReconnectOverrideSource>,
|
||||||
|
request_channel_capacity: std::option::Option<usize>,
|
||||||
|
update_channel_capacity: std::option::Option<usize>,
|
||||||
|
max_inbound_message_size_bytes: std::option::Option<usize>,
|
||||||
|
max_outbound_message_size_bytes: std::option::Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcMetadataSource {
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveGrpcEndpointSource {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
provider: String,
|
||||||
|
cluster: String,
|
||||||
|
protocol: String,
|
||||||
|
url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
metadata: std::vec::Vec<EffectiveGrpcMetadataSource>,
|
||||||
|
#[serde(default)]
|
||||||
|
secret_metadata: std::vec::Vec<EffectiveGrpcMetadataSource>,
|
||||||
|
#[serde(default)]
|
||||||
|
session: std::option::Option<EffectiveGrpcSessionOverrideSource>,
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedTransportConfig> {
|
fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedTransportConfig> {
|
||||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapping standard Transport Config profile");
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapping standard Transport Config profile");
|
||||||
let effective = profile.resolve_effective_environment_detailed(environment);
|
let effective = profile.resolve_effective_environment_detailed(environment);
|
||||||
@@ -294,16 +391,26 @@ fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
|
let grpc_settings = map_optional_grpc_settings(format_version, source.grpc_defaults, source.grpc_endpoints, &effective, profile);
|
||||||
|
let grpc_settings = match grpc_settings {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
let ws_endpoint_count = match ws_settings.as_ref() {
|
let ws_endpoint_count = match ws_settings.as_ref() {
|
||||||
std::option::Option::Some(value) => value.endpoints().len(),
|
std::option::Option::Some(value) => value.endpoints().len(),
|
||||||
std::option::Option::None => 0_usize,
|
std::option::Option::None => 0_usize,
|
||||||
};
|
};
|
||||||
|
let grpc_endpoint_count = match grpc_settings.as_ref() {
|
||||||
|
std::option::Option::Some(value) => value.endpoints().len(),
|
||||||
|
std::option::Option::None => 0_usize,
|
||||||
|
};
|
||||||
ksp_logging_lib::debug!(
|
ksp_logging_lib::debug!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
profile_id = profile.profile_id(),
|
profile_id = profile.profile_id(),
|
||||||
format_version,
|
format_version,
|
||||||
http_endpoint_count = settings.endpoints().len(),
|
http_endpoint_count = settings.endpoints().len(),
|
||||||
ws_endpoint_count,
|
ws_endpoint_count,
|
||||||
|
grpc_endpoint_count,
|
||||||
"mapped standard Transport Config to runtime settings"
|
"mapped standard Transport Config to runtime settings"
|
||||||
);
|
);
|
||||||
return std::result::Result::Ok(ResolvedTransportConfig {
|
return std::result::Result::Ok(ResolvedTransportConfig {
|
||||||
@@ -314,6 +421,7 @@ fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment
|
|||||||
effective,
|
effective,
|
||||||
settings,
|
settings,
|
||||||
ws_settings,
|
ws_settings,
|
||||||
|
grpc_settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,18 +436,22 @@ fn map_optional_ws_settings(
|
|||||||
if defaults.is_some() || sources.is_some() {
|
if defaults.is_some() || sources.is_some() {
|
||||||
std::result::Result::Err(effective_error(profile, "Transport V1 must remain HTTP-only"))
|
std::result::Result::Err(effective_error(profile, "Transport V1 must remain HTTP-only"))
|
||||||
} else {
|
} else {
|
||||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapped backward-compatible Transport V1 without WebSocket settings");
|
ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
profile_id = profile.profile_id(),
|
||||||
|
"mapped backward-compatible Transport V1 without WebSocket settings"
|
||||||
|
);
|
||||||
std::result::Result::Ok(std::option::Option::None)
|
std::result::Result::Ok(std::option::Option::None)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
2 => {
|
2 | 3 => {
|
||||||
let defaults = match defaults {
|
let defaults = match defaults {
|
||||||
std::option::Option::Some(value) => value,
|
std::option::Option::Some(value) => value,
|
||||||
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2 requires ws_defaults")),
|
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2/V3 requires ws_defaults")),
|
||||||
};
|
};
|
||||||
let sources = match sources {
|
let sources = match sources {
|
||||||
std::option::Option::Some(value) => value,
|
std::option::Option::Some(value) => value,
|
||||||
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2 profile requires ws_endpoints")),
|
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2/V3 profile requires ws_endpoints")),
|
||||||
};
|
};
|
||||||
let endpoints = map_ws_endpoints(sources, &defaults, profile);
|
let endpoints = map_ws_endpoints(sources, &defaults, profile);
|
||||||
let endpoints = match endpoints {
|
let endpoints = match endpoints {
|
||||||
@@ -360,6 +472,283 @@ fn map_optional_ws_settings(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_optional_grpc_settings(
|
||||||
|
format_version: u32,
|
||||||
|
defaults: std::option::Option<EffectiveGrpcSessionSource>,
|
||||||
|
sources: std::option::Option<std::vec::Vec<EffectiveGrpcEndpointSource>>,
|
||||||
|
effective: &crate::ResolvedConfigJson,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
) -> ksp_core_lib::Result<std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcTransportSettings>> {
|
||||||
|
return match format_version {
|
||||||
|
1 | 2 => {
|
||||||
|
if defaults.is_some() || sources.is_some() {
|
||||||
|
std::result::Result::Err(effective_error(profile, "Transport V1/V2 must not define Yellowstone gRPC settings"))
|
||||||
|
} else {
|
||||||
|
std::result::Result::Ok(std::option::Option::None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
3 => {
|
||||||
|
let defaults = match defaults {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V3 requires grpc_defaults")),
|
||||||
|
};
|
||||||
|
let sources = match sources {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||||
|
};
|
||||||
|
let endpoints = map_grpc_endpoints(sources, &defaults, effective, profile);
|
||||||
|
let endpoints = match endpoints {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let settings = ksp_onchain_transport_lib::YellowstoneGrpcTransportSettings::new(endpoints);
|
||||||
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
|
return std::result::Result::Err(transport_contract_error(
|
||||||
|
profile,
|
||||||
|
"effective Yellowstone gRPC Transport settings fail the Transport runtime contract",
|
||||||
|
&error,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(settings))
|
||||||
|
},
|
||||||
|
_ => std::result::Result::Err(effective_error(profile, "effective Transport format_version is unsupported")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_grpc_endpoints(
|
||||||
|
sources: std::vec::Vec<EffectiveGrpcEndpointSource>,
|
||||||
|
defaults: &EffectiveGrpcSessionSource,
|
||||||
|
effective: &crate::ResolvedConfigJson,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings>> {
|
||||||
|
let mut endpoints = std::vec::Vec::<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings>::with_capacity(sources.len());
|
||||||
|
for (endpoint_index, source) in sources.into_iter().enumerate() {
|
||||||
|
let endpoint_name = source.name.clone();
|
||||||
|
if source.protocol != "solana_yellowstone" {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
effective_error(profile, "effective gRPC protocol is unsupported")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("grpc_protocol", source.protocol),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let url = ksp_onchain_transport_lib::YellowstoneGrpcEndpointUrl::parse(source.url);
|
||||||
|
let url = match url {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective Yellowstone gRPC endpoint URL is invalid", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let session = map_grpc_session_settings(defaults, source.session.as_ref(), profile, endpoint_name.as_str());
|
||||||
|
let session = match session {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let metadata = map_grpc_metadata(source.metadata, source.secret_metadata, effective, profile, endpoint_name.as_str(), endpoint_index);
|
||||||
|
let metadata = match metadata {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let endpoint = ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings::new(
|
||||||
|
source.name,
|
||||||
|
source.enabled,
|
||||||
|
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new(source.provider),
|
||||||
|
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new(source.cluster),
|
||||||
|
url,
|
||||||
|
session,
|
||||||
|
);
|
||||||
|
let endpoint = endpoint.with_metadata(metadata);
|
||||||
|
let endpoint = match endpoint {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective Yellowstone gRPC metadata fail the Transport runtime contract", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
endpoints.push(endpoint);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_grpc_session_settings(
|
||||||
|
defaults: &EffectiveGrpcSessionSource,
|
||||||
|
overrides: std::option::Option<&EffectiveGrpcSessionOverrideSource>,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings> {
|
||||||
|
let mut connect_timeout_ms = defaults.connect_timeout_ms;
|
||||||
|
let mut unary_timeout_ms = defaults.unary_timeout_ms;
|
||||||
|
let mut close_timeout_ms = defaults.close_timeout_ms;
|
||||||
|
let mut reconnect_max_retries = defaults.reconnect.max_retries;
|
||||||
|
let mut reconnect_initial_backoff_ms = defaults.reconnect.initial_backoff_ms;
|
||||||
|
let mut reconnect_max_backoff_ms = defaults.reconnect.max_backoff_ms;
|
||||||
|
let mut request_channel_capacity = defaults.request_channel_capacity;
|
||||||
|
let mut update_channel_capacity = defaults.update_channel_capacity;
|
||||||
|
let mut max_inbound_message_size_bytes = defaults.max_inbound_message_size_bytes;
|
||||||
|
let mut max_outbound_message_size_bytes = defaults.max_outbound_message_size_bytes;
|
||||||
|
if let std::option::Option::Some(overrides) = overrides {
|
||||||
|
if let std::option::Option::Some(value) = overrides.connect_timeout_ms {
|
||||||
|
connect_timeout_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.unary_timeout_ms {
|
||||||
|
unary_timeout_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.close_timeout_ms {
|
||||||
|
close_timeout_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(reconnect) = overrides.reconnect.as_ref() {
|
||||||
|
if let std::option::Option::Some(value) = reconnect.max_retries {
|
||||||
|
reconnect_max_retries = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = reconnect.initial_backoff_ms {
|
||||||
|
reconnect_initial_backoff_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = reconnect.max_backoff_ms {
|
||||||
|
reconnect_max_backoff_ms = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.request_channel_capacity {
|
||||||
|
request_channel_capacity = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.update_channel_capacity {
|
||||||
|
update_channel_capacity = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_inbound_message_size_bytes {
|
||||||
|
max_inbound_message_size_bytes = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_outbound_message_size_bytes {
|
||||||
|
max_outbound_message_size_bytes = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let reconnect = ksp_onchain_transport_lib::YellowstoneGrpcReconnectSettings::new(
|
||||||
|
reconnect_max_retries,
|
||||||
|
std::time::Duration::from_millis(reconnect_initial_backoff_ms),
|
||||||
|
std::time::Duration::from_millis(reconnect_max_backoff_ms),
|
||||||
|
);
|
||||||
|
let settings = ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::new(
|
||||||
|
std::time::Duration::from_millis(connect_timeout_ms),
|
||||||
|
std::time::Duration::from_millis(unary_timeout_ms),
|
||||||
|
std::time::Duration::from_millis(close_timeout_ms),
|
||||||
|
reconnect,
|
||||||
|
request_channel_capacity,
|
||||||
|
update_channel_capacity,
|
||||||
|
max_inbound_message_size_bytes,
|
||||||
|
max_outbound_message_size_bytes,
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective Yellowstone gRPC session settings fail the Transport runtime contract", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_grpc_metadata(
|
||||||
|
public_sources: std::vec::Vec<EffectiveGrpcMetadataSource>,
|
||||||
|
secret_sources: std::vec::Vec<EffectiveGrpcMetadataSource>,
|
||||||
|
effective: &crate::ResolvedConfigJson,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
endpoint_index: usize,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry>> {
|
||||||
|
let mut metadata = std::vec::Vec::<ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry>::with_capacity(public_sources.len() + secret_sources.len());
|
||||||
|
for (metadata_index, source) in public_sources.into_iter().enumerate() {
|
||||||
|
let pointer = format!("/grpc_endpoints/{endpoint_index}/metadata/{metadata_index}/value");
|
||||||
|
if let std::result::Result::Err(error) = validate_grpc_metadata_provenance(effective, pointer.as_str(), false, profile, endpoint_name) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let entry = ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry::public(source.key, source.value);
|
||||||
|
let entry = match entry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective public Yellowstone gRPC metadata are invalid", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
metadata.push(entry);
|
||||||
|
}
|
||||||
|
for (metadata_index, source) in secret_sources.into_iter().enumerate() {
|
||||||
|
let pointer = format!("/grpc_endpoints/{endpoint_index}/secret_metadata/{metadata_index}/value");
|
||||||
|
if let std::result::Result::Err(error) = validate_grpc_metadata_provenance(effective, pointer.as_str(), true, profile, endpoint_name) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let entry = ksp_onchain_transport_lib::YellowstoneGrpcMetadataEntry::secret(source.key, source.value);
|
||||||
|
let entry = match entry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective secret Yellowstone gRPC metadata are invalid", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
metadata.push(entry);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_grpc_metadata_provenance(
|
||||||
|
effective: &crate::ResolvedConfigJson,
|
||||||
|
pointer: &str,
|
||||||
|
secret_class: bool,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let provenance = match effective.provenance_at(pointer) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
effective_error(profile, "Yellowstone gRPC metadata provenance is unavailable")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("field", pointer),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut has_secret_environment = false;
|
||||||
|
for item in provenance {
|
||||||
|
let variable_name = match item.variable_name() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => continue,
|
||||||
|
};
|
||||||
|
let sensitivity = crate::ConfigSensitivity::from_variable_name(variable_name);
|
||||||
|
let sensitivity = match sensitivity {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
if sensitivity.is_secret() {
|
||||||
|
has_secret_environment = true;
|
||||||
|
if !secret_class {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
effective_error(profile, "public Yellowstone gRPC metadata must not derive from a secret environment variable")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("field", pointer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if secret_class {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
effective_error(profile, "secret Yellowstone gRPC metadata may reference only secret environment variables")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("field", pointer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secret_class && !has_secret_environment {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
effective_error(profile, "secret Yellowstone gRPC metadata require secret environment provenance")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("field", pointer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
fn map_endpoints(
|
fn map_endpoints(
|
||||||
sources: std::vec::Vec<EffectiveEndpointSource>,
|
sources: std::vec::Vec<EffectiveEndpointSource>,
|
||||||
profile: &crate::ResolvedConfigProfile,
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/tests/public_api.rs
|
// file: crates/ksp-config-lib/tests/public_api.rs
|
||||||
// version: 22
|
// version: 23
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
|
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
|
||||||
//! Logging/Transport adapters and management contracts.
|
//! Logging/Transport adapters and management contracts.
|
||||||
@@ -254,7 +254,9 @@ fn transport_adapter_contract_is_available_from_crate_root() {
|
|||||||
assert!(std::mem::size_of::<ksp_config_lib::ResolvedTransportConfig>() > 0);
|
assert!(std::mem::size_of::<ksp_config_lib::ResolvedTransportConfig>() > 0);
|
||||||
let _http_settings = ksp_config_lib::ResolvedTransportConfig::http_settings;
|
let _http_settings = ksp_config_lib::ResolvedTransportConfig::http_settings;
|
||||||
let _ws_settings = ksp_config_lib::ResolvedTransportConfig::ws_settings;
|
let _ws_settings = ksp_config_lib::ResolvedTransportConfig::ws_settings;
|
||||||
|
let _grpc_settings = ksp_config_lib::ResolvedTransportConfig::grpc_settings;
|
||||||
let _into_transport_settings = ksp_config_lib::ResolvedTransportConfig::into_transport_settings;
|
let _into_transport_settings = ksp_config_lib::ResolvedTransportConfig::into_transport_settings;
|
||||||
|
let _into_all_transport_settings = ksp_config_lib::ResolvedTransportConfig::into_all_transport_settings;
|
||||||
assert_eq!(ksp_config_lib::FILE_ID_STD_TRANSPORT, "cfg.std.transport");
|
assert_eq!(ksp_config_lib::FILE_ID_STD_TRANSPORT, "cfg.std.transport");
|
||||||
assert_eq!(ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT, "schema.std.transport");
|
assert_eq!(ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT, "schema.std.transport");
|
||||||
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_FILENAME, "std.transport.json");
|
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_FILENAME, "std.transport.json");
|
||||||
|
|||||||
242
crates/ksp-config-lib/unit_tests/fixtures_v3/std.transport.json
Normal file
242
crates/ksp-config-lib/unit_tests/fixtures_v3/std.transport.json
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
{
|
||||||
|
"format_version": 3,
|
||||||
|
"retry": {
|
||||||
|
"max_retries": 2,
|
||||||
|
"initial_backoff_ms": 100,
|
||||||
|
"max_backoff_ms": 2000
|
||||||
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"command_timeout_ms": 8000,
|
||||||
|
"close_timeout_ms": 4000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 5,
|
||||||
|
"initial_backoff_ms": 250,
|
||||||
|
"max_backoff_ms": 5000
|
||||||
|
},
|
||||||
|
"resubscribe": "active_subscriptions",
|
||||||
|
"command_queue_capacity": 64,
|
||||||
|
"notification_queue_capacity": 96,
|
||||||
|
"max_active_subscriptions": 256,
|
||||||
|
"max_pending_requests": 48,
|
||||||
|
"max_message_size_bytes": 33554432,
|
||||||
|
"max_frame_size_bytes": 8388608,
|
||||||
|
"max_write_buffer_size_bytes": 524288
|
||||||
|
},
|
||||||
|
"grpc_defaults": {
|
||||||
|
"connect_timeout_ms": 6000,
|
||||||
|
"unary_timeout_ms": 7000,
|
||||||
|
"close_timeout_ms": 4000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 4,
|
||||||
|
"initial_backoff_ms": 200,
|
||||||
|
"max_backoff_ms": 3000
|
||||||
|
},
|
||||||
|
"request_channel_capacity": 64,
|
||||||
|
"update_channel_capacity": 96,
|
||||||
|
"max_inbound_message_size_bytes": 33554432,
|
||||||
|
"max_outbound_message_size_bytes": 16777216
|
||||||
|
},
|
||||||
|
"default_profile": "grpc_secret_ok",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "grpc_secret_ok",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_http",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "https://fallback.invalid",
|
||||||
|
"connect_timeout_ms": 1000,
|
||||||
|
"request_timeout_ms": 3000,
|
||||||
|
"max_idle_connections_per_host": 2,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"priority": 1,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 5,
|
||||||
|
"max_concurrent_requests": 2,
|
||||||
|
"pause_after_rate_limit_ms": 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "wss://fallback.invalid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grpc_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_grpc",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"protocol": "solana_yellowstone",
|
||||||
|
"url": "https://grpc.invalid",
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"key": "x-ksp-label",
|
||||||
|
"value": "${KSP_PUBLIC_GRPC_LABEL:-fixture-public}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_metadata": [
|
||||||
|
{
|
||||||
|
"key": "x-ksp-token",
|
||||||
|
"value": "Bearer ${KSP_SECRET_GRPC_TOKEN:-fixture-secret}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"session": {
|
||||||
|
"connect_timeout_ms": 4500,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 7
|
||||||
|
},
|
||||||
|
"update_channel_capacity": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "grpc_public_from_secret_invalid",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_http",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "https://fallback.invalid",
|
||||||
|
"connect_timeout_ms": 1000,
|
||||||
|
"request_timeout_ms": 3000,
|
||||||
|
"max_idle_connections_per_host": 2,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"priority": 1,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 5,
|
||||||
|
"max_concurrent_requests": 2,
|
||||||
|
"pause_after_rate_limit_ms": 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "wss://fallback.invalid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grpc_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_grpc",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"protocol": "solana_yellowstone",
|
||||||
|
"url": "https://grpc.invalid",
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"key": "x-ksp-public",
|
||||||
|
"value": "${KSP_SECRET_GRPC_TOKEN:-fixture-secret}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_metadata": [],
|
||||||
|
"session": {
|
||||||
|
"connect_timeout_ms": 4500,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 7
|
||||||
|
},
|
||||||
|
"update_channel_capacity": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "grpc_secret_from_public_invalid",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_http",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "https://fallback.invalid",
|
||||||
|
"connect_timeout_ms": 1000,
|
||||||
|
"request_timeout_ms": 3000,
|
||||||
|
"max_idle_connections_per_host": 2,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"priority": 1,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 5,
|
||||||
|
"max_concurrent_requests": 2,
|
||||||
|
"pause_after_rate_limit_ms": 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "wss://fallback.invalid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grpc_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_grpc",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"protocol": "solana_yellowstone",
|
||||||
|
"url": "https://grpc.invalid",
|
||||||
|
"metadata": [],
|
||||||
|
"secret_metadata": [
|
||||||
|
{
|
||||||
|
"key": "x-ksp-secret",
|
||||||
|
"value": "${KSP_PUBLIC_GRPC_LABEL:-fixture-public}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"session": {
|
||||||
|
"connect_timeout_ms": 4500,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 7
|
||||||
|
},
|
||||||
|
"update_channel_capacity": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fixture_transport_profile_maps_complete_runtime_contract() {
|
fn fixture_transport_profile_maps_complete_runtime_contract() {
|
||||||
@@ -41,6 +41,7 @@ fn fixture_transport_profile_maps_complete_runtime_contract() {
|
|||||||
assert_eq!(role.limits().burst_capacity().map(std::num::NonZeroU32::get), std::option::Option::Some(12));
|
assert_eq!(role.limits().burst_capacity().map(std::num::NonZeroU32::get), std::option::Option::Some(12));
|
||||||
assert_eq!(role.limits().max_concurrent_requests().map(std::num::NonZeroU32::get), std::option::Option::Some(4));
|
assert_eq!(role.limits().max_concurrent_requests().map(std::num::NonZeroU32::get), std::option::Option::Some(4));
|
||||||
assert_eq!(role.limits().pause_after_rate_limit(), std::option::Option::Some(std::time::Duration::from_millis(650)));
|
assert_eq!(role.limits().pause_after_rate_limit(), std::option::Option::Some(std::time::Duration::from_millis(650)));
|
||||||
|
assert!(resolved.grpc_settings().is_none(), "V2 fixture must remain Yellowstone gRPC-free");
|
||||||
let ws = resolved.ws_settings();
|
let ws = resolved.ws_settings();
|
||||||
assert!(ws.is_some(), "V2 fixture should expose WebSocket settings");
|
assert!(ws.is_some(), "V2 fixture should expose WebSocket settings");
|
||||||
if let std::option::Option::Some(ws) = ws {
|
if let std::option::Option::Some(ws) = ws {
|
||||||
@@ -90,6 +91,7 @@ fn v1_transport_fixture_remains_backward_readable_and_http_only() {
|
|||||||
assert_eq!(resolved.settings().endpoints().len(), 1);
|
assert_eq!(resolved.settings().endpoints().len(), 1);
|
||||||
assert_eq!(resolved.settings().endpoints()[0].url().as_str(), "https://legacy.invalid");
|
assert_eq!(resolved.settings().endpoints()[0].url().as_str(), "https://legacy.invalid");
|
||||||
assert!(resolved.ws_settings().is_none(), "V1 must not invent WebSocket runtime settings");
|
assert!(resolved.ws_settings().is_none(), "V1 must not invent WebSocket runtime settings");
|
||||||
|
assert!(resolved.grpc_settings().is_none(), "V1 must not invent Yellowstone gRPC runtime settings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +111,9 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
|||||||
assert_eq!(default.profile_id(), "devnet_public");
|
assert_eq!(default.profile_id(), "devnet_public");
|
||||||
assert_eq!(default.settings().endpoints()[0].cluster().as_str(), "devnet");
|
assert_eq!(default.settings().endpoints()[0].cluster().as_str(), "devnet");
|
||||||
assert_eq!(default.settings().endpoints()[0].url().as_str(), "https://api.devnet.solana.com");
|
assert_eq!(default.settings().endpoints()[0].url().as_str(), "https://api.devnet.solana.com");
|
||||||
|
assert!(default.grpc_settings().is_none(), "Devnet profile must not invent a Yellowstone gRPC endpoint");
|
||||||
let ws = default.ws_settings();
|
let ws = default.ws_settings();
|
||||||
assert!(ws.is_some(), "committed V2 Devnet profile should expose WebSocket settings");
|
assert!(ws.is_some(), "committed V3 Devnet profile should expose WebSocket settings");
|
||||||
if let std::option::Option::Some(ws) = ws {
|
if let std::option::Option::Some(ws) = ws {
|
||||||
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.devnet.solana.com");
|
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.devnet.solana.com");
|
||||||
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||||
@@ -121,8 +124,9 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
|||||||
assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
||||||
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet-beta");
|
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet-beta");
|
||||||
assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet-beta.solana.com");
|
assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet-beta.solana.com");
|
||||||
|
assert!(mainnet.grpc_settings().is_none(), "generic Mainnet profile must stay distinct from the PublicNode gRPC profile");
|
||||||
let ws = mainnet.ws_settings();
|
let ws = mainnet.ws_settings();
|
||||||
assert!(ws.is_some(), "committed V2 Mainnet profile should expose WebSocket settings");
|
assert!(ws.is_some(), "committed V3 Mainnet profile should expose WebSocket settings");
|
||||||
if let std::option::Option::Some(ws) = ws {
|
if let std::option::Option::Some(ws) = ws {
|
||||||
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet-beta.solana.com");
|
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet-beta.solana.com");
|
||||||
}
|
}
|
||||||
@@ -130,7 +134,121 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn committed_v2_websocket_endpoint_composes_with_public_session_constructor_without_polling() {
|
fn committed_v3_publicnode_mainnet_maps_provider_neutral_yellowstone_grpc() {
|
||||||
|
let engine = committed_engine();
|
||||||
|
let engine = match engine {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||||
|
let resolved = engine.load_resolved_transport_config(std::option::Option::Some("publicnode_mainnet"), &environment);
|
||||||
|
assert!(resolved.is_ok(), "committed PublicNode Mainnet V3 profile should map: {resolved:?}");
|
||||||
|
let resolved = match resolved {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(resolved.profile_id(), "publicnode_mainnet");
|
||||||
|
assert!(resolved.ws_settings().is_some());
|
||||||
|
let grpc = resolved.grpc_settings();
|
||||||
|
assert!(grpc.is_some(), "PublicNode Mainnet profile should expose Yellowstone gRPC settings");
|
||||||
|
if let std::option::Option::Some(grpc) = grpc {
|
||||||
|
assert_eq!(grpc.endpoints().len(), 1);
|
||||||
|
let endpoint = &grpc.endpoints()[0];
|
||||||
|
assert_eq!(endpoint.name(), "publicnode_solana_mainnet_yellowstone");
|
||||||
|
assert_eq!(endpoint.provider().as_str(), "publicnode");
|
||||||
|
assert_eq!(endpoint.cluster().as_str(), "mainnet-beta");
|
||||||
|
assert_eq!(endpoint.url().as_str(), "https://solana-yellowstone-grpc.publicnode.com:443");
|
||||||
|
assert!(endpoint.url().uses_tls());
|
||||||
|
assert!(endpoint.metadata().is_empty());
|
||||||
|
assert_eq!(endpoint.session().connect_timeout(), std::time::Duration::from_millis(10_000));
|
||||||
|
assert_eq!(endpoint.session().unary_timeout(), std::time::Duration::from_millis(10_000));
|
||||||
|
assert_eq!(endpoint.session().close_timeout(), std::time::Duration::from_millis(5_000));
|
||||||
|
assert_eq!(endpoint.session().reconnect().max_retries(), 5);
|
||||||
|
assert!(grpc.validate().is_ok(), "Config-produced Yellowstone gRPC settings should satisfy Transport validation");
|
||||||
|
let debug = format!("{grpc:?}");
|
||||||
|
assert!(!debug.contains("solana-yellowstone-grpc.publicnode.com"), "Transport settings Debug must not expose gRPC endpoint URLs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v3_grpc_metadata_separates_public_and_secret_provenance() {
|
||||||
|
let engine = v3_fixture_engine();
|
||||||
|
let engine = match engine {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let canary = "grpc-secret-canary";
|
||||||
|
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||||
|
process.insert("KSP_SECRET_GRPC_TOKEN".to_owned(), canary.to_owned());
|
||||||
|
process.insert("KSP_PUBLIC_GRPC_LABEL".to_owned(), "operator-public".to_owned());
|
||||||
|
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||||
|
let resolved = engine.load_resolved_transport_config(std::option::Option::None, &environment);
|
||||||
|
assert!(resolved.is_ok(), "V3 gRPC metadata fixture should map: {resolved:?}");
|
||||||
|
let resolved = match resolved {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let grpc = resolved.grpc_settings();
|
||||||
|
assert!(grpc.is_some());
|
||||||
|
if let std::option::Option::Some(grpc) = grpc {
|
||||||
|
let endpoint = &grpc.endpoints()[0];
|
||||||
|
assert_eq!(endpoint.metadata().len(), 2);
|
||||||
|
assert_eq!(endpoint.metadata()[0].key(), "x-ksp-label");
|
||||||
|
assert!(!endpoint.metadata()[0].is_secret());
|
||||||
|
assert_eq!(endpoint.metadata()[1].key(), "x-ksp-token");
|
||||||
|
assert!(endpoint.metadata()[1].is_secret());
|
||||||
|
assert_eq!(endpoint.session().connect_timeout(), std::time::Duration::from_millis(4_500));
|
||||||
|
assert_eq!(endpoint.session().unary_timeout(), std::time::Duration::from_millis(7_000));
|
||||||
|
assert_eq!(endpoint.session().reconnect().max_retries(), 7);
|
||||||
|
assert_eq!(endpoint.session().update_channel_capacity(), 32);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
resolved.effective().safe_value().pointer("/grpc_endpoints/0/metadata/0/value").and_then(serde_json::Value::as_str),
|
||||||
|
std::option::Option::Some("operator-public")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolved.effective().safe_value().pointer("/grpc_endpoints/0/secret_metadata/0/value").and_then(serde_json::Value::as_str),
|
||||||
|
std::option::Option::Some("Bearer ********")
|
||||||
|
);
|
||||||
|
let secret_provenance = resolved.effective().provenance_at("/grpc_endpoints/0/secret_metadata/0/value");
|
||||||
|
assert!(secret_provenance.is_some());
|
||||||
|
if let std::option::Option::Some(provenance) = secret_provenance {
|
||||||
|
assert_eq!(provenance.len(), 2);
|
||||||
|
assert_eq!(provenance[0], crate::ConfigValueProvenance::DocumentLiteral);
|
||||||
|
assert_eq!(provenance[1].variable_name(), std::option::Option::Some("KSP_SECRET_GRPC_TOKEN"));
|
||||||
|
}
|
||||||
|
let debug = format!("{resolved:?}");
|
||||||
|
assert!(!debug.contains(canary));
|
||||||
|
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v3_grpc_metadata_rejects_crossed_sensitivity_classes() {
|
||||||
|
let engine = v3_fixture_engine();
|
||||||
|
let engine = match engine {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||||
|
process.insert("KSP_SECRET_GRPC_TOKEN".to_owned(), "secret-canary".to_owned());
|
||||||
|
process.insert("KSP_PUBLIC_GRPC_LABEL".to_owned(), "public-canary".to_owned());
|
||||||
|
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||||
|
let public_from_secret = engine.load_resolved_transport_config(std::option::Option::Some("grpc_public_from_secret_invalid"), &environment);
|
||||||
|
let secret_from_public = engine.load_resolved_transport_config(std::option::Option::Some("grpc_secret_from_public_invalid"), &environment);
|
||||||
|
assert!(public_from_secret.is_err(), "public metadata must reject KSP_SECRET provenance");
|
||||||
|
assert!(secret_from_public.is_err(), "secret_metadata must reject non-secret environment provenance");
|
||||||
|
if let std::result::Result::Err(error) = public_from_secret {
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
|
||||||
|
assert!(!format!("{error:?}").contains("secret-canary"));
|
||||||
|
}
|
||||||
|
if let std::result::Result::Err(error) = secret_from_public {
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
|
||||||
|
assert!(!format!("{error:?}").contains("public-canary"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn committed_v3_websocket_endpoint_preserves_v2_compatibility_constructor_without_polling() {
|
||||||
let engine = committed_engine();
|
let engine = committed_engine();
|
||||||
let engine = match engine {
|
let engine = match engine {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -138,11 +256,11 @@ fn committed_v2_websocket_endpoint_composes_with_public_session_constructor_with
|
|||||||
};
|
};
|
||||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||||
let resolved = engine.load_resolved_transport_config(std::option::Option::Some("devnet_public"), &environment);
|
let resolved = engine.load_resolved_transport_config(std::option::Option::Some("devnet_public"), &environment);
|
||||||
assert!(resolved.is_ok(), "committed V2 Transport profile should map: {resolved:?}");
|
assert!(resolved.is_ok(), "committed V3 Transport profile should map: {resolved:?}");
|
||||||
if let std::result::Result::Ok(resolved) = resolved {
|
if let std::result::Result::Ok(resolved) = resolved {
|
||||||
let (http, ws) = resolved.into_transport_settings();
|
let (http, ws) = resolved.into_transport_settings();
|
||||||
assert_eq!(http.endpoints().len(), 1);
|
assert_eq!(http.endpoints().len(), 1);
|
||||||
assert!(ws.is_some(), "committed V2 Transport profile should expose WebSocket settings");
|
assert!(ws.is_some(), "committed V3 Transport profile should expose WebSocket settings");
|
||||||
if let std::option::Option::Some(ws) = ws {
|
if let std::option::Option::Some(ws) = ws {
|
||||||
assert!(ws.validate().is_ok(), "Config-produced WebSocket settings should satisfy Transport validation");
|
assert!(ws.validate().is_ok(), "Config-produced WebSocket settings should satisfy Transport validation");
|
||||||
assert_eq!(ws.endpoints().len(), 1);
|
assert_eq!(ws.endpoints().len(), 1);
|
||||||
@@ -170,6 +288,7 @@ fn transport_profile_preserves_global_and_profile_origin() {
|
|||||||
if let std::result::Result::Ok(profile) = profile {
|
if let std::result::Result::Ok(profile) = profile {
|
||||||
assert_eq!(profile.origin("retry"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
assert_eq!(profile.origin("retry"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
assert_eq!(profile.origin("ws_defaults"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
assert_eq!(profile.origin("ws_defaults"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
|
assert_eq!(profile.origin("grpc_defaults"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
assert_eq!(profile.origin("endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
assert_eq!(profile.origin("endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
||||||
assert_eq!(profile.origin("ws_endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
assert_eq!(profile.origin("ws_endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
||||||
assert_eq!(profile.origin("format_version"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
assert_eq!(profile.origin("format_version"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
@@ -421,6 +540,22 @@ fn v1_fixture_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|||||||
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn v3_fixture_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||||
|
let workspace = workspace_root();
|
||||||
|
let fixture_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("unit_tests/fixtures_v3");
|
||||||
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture_root, workspace.join("config/schemas"));
|
||||||
|
let bootstrap = match bootstrap {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let registry = crate::ConfigFileRegistry::defaults();
|
||||||
|
let registry = match registry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
||||||
|
}
|
||||||
|
|
||||||
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||||
let workspace = workspace_root();
|
let workspace = workspace_root();
|
||||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||||
|
|||||||
284
deltas/0.2.9/pre.011.md
Normal file
284
deltas/0.2.9/pre.011.md
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<!-- file: deltas/0.2.9/pre.011.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.9-pre.011` — Config Transport V3 + PublicNode Mainnet + consolidation documentaire
|
||||||
|
|
||||||
|
## 1. Base et version
|
||||||
|
|
||||||
|
Base immédiate :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.9-pre.010-fix.001
|
||||||
|
Cargo workspace = 0.2.9-pre.10.fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Version candidate :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.9-pre.011
|
||||||
|
Cargo workspace = 0.2.9-pre.11
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.010-fix.001` est entièrement vert : fmt/audit/check/Clippy sans warning, Transport 383 unit + 49 public API + 43 release-completeness + 4 doctests, dependency canary 3/3 et `cargo test --workspace` PASS.
|
||||||
|
|
||||||
|
## 2. Objet
|
||||||
|
|
||||||
|
Cette tranche matérialise la frontière Config décidée par `pre.001` puis différée jusqu'après le lifecycle/reconnect Yellowstone :
|
||||||
|
|
||||||
|
```text
|
||||||
|
std.transport V3
|
||||||
|
Config -> YellowstoneGrpcTransportSettings
|
||||||
|
provider et protocol distincts
|
||||||
|
metadata publique et secrète avec provenance contrôlée
|
||||||
|
premier profil PublicNode Mainnet standard
|
||||||
|
backward V1/V2 strict
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle profite aussi du changement de phase de la release pour remettre le plan `016` et la validation `012` dans un état maintenable. Les détails historiques ne sont pas supprimés : ils restent dans les deltas immuables `deltas/0.2.9/`.
|
||||||
|
|
||||||
|
## 3. Config Transport V3
|
||||||
|
|
||||||
|
### 3.1 Schema
|
||||||
|
|
||||||
|
`config/schemas/std.transport.schema.json` devient `urn:ksp:schema:std.transport:v3` et conserve trois branches strictes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V1 = HTTP-only
|
||||||
|
V2 = HTTP + WebSocket
|
||||||
|
V3 = HTTP + WebSocket + Yellowstone gRPC optionnel par profil
|
||||||
|
```
|
||||||
|
|
||||||
|
V3 ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
grpc_defaults
|
||||||
|
grpc_endpoints[]?
|
||||||
|
```
|
||||||
|
|
||||||
|
`grpc_defaults` mappe uniquement des knobs déjà possédés par `YellowstoneGrpcSessionSettings` : timeouts, reconnect, channel capacities et max message sizes. Les bounds des filtres Subscribe restent un contrat Transport et ne sont pas transformés en configuration arbitraire.
|
||||||
|
|
||||||
|
Chaque endpoint gRPC contient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
name
|
||||||
|
enabled
|
||||||
|
provider
|
||||||
|
cluster
|
||||||
|
protocol = solana_yellowstone
|
||||||
|
url
|
||||||
|
metadata[]?
|
||||||
|
secret_metadata[]?
|
||||||
|
session?
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Mapping Config -> Transport
|
||||||
|
|
||||||
|
`ResolvedTransportConfig` ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
grpc_settings()
|
||||||
|
into_all_transport_settings()
|
||||||
|
```
|
||||||
|
|
||||||
|
L'accesseur historique suivant est volontairement inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
into_transport_settings() -> (HttpTransportSettings, Option<WsTransportSettings>)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cela évite une rupture source silencieuse des consumers V2.
|
||||||
|
|
||||||
|
V1 et V2 retournent toujours `None` pour gRPC. Un profil V3 qui n'a pas de `grpc_endpoints` retourne également `None` au lieu d'inventer un Transport gRPC vide.
|
||||||
|
|
||||||
|
### 3.3 Protocol vs provider
|
||||||
|
|
||||||
|
Config exige actuellement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
protocol = solana_yellowstone
|
||||||
|
```
|
||||||
|
|
||||||
|
Le champ `provider` reste descriptif et mappe vers `YellowstoneGrpcProviderName`.
|
||||||
|
|
||||||
|
Donc :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PublicNode != protocole
|
||||||
|
PublicNode != nouvelle façade Transport
|
||||||
|
PublicNode = environnement d'exécution du standard Yellowstone
|
||||||
|
```
|
||||||
|
|
||||||
|
Toute future divergence provider doit rester un delta N3 explicite.
|
||||||
|
|
||||||
|
### 3.4 Metadata et provenance
|
||||||
|
|
||||||
|
Deux classes sont matérialisées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
metadata
|
||||||
|
secret_metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
Règles avant mapping :
|
||||||
|
|
||||||
|
```text
|
||||||
|
metadata + provenance KSP_SECRET_*/KSPB_SECRET_* -> reject
|
||||||
|
secret_metadata sans provenance secret -> reject
|
||||||
|
secret_metadata + provenance non-secret env -> reject
|
||||||
|
secret_metadata + provenance secret -> accept
|
||||||
|
```
|
||||||
|
|
||||||
|
Les segments littéraux autour d'un placeholder secret restent permis. Config possède la provenance et la projection sûre ; Transport reçoit ensuite seulement les `YellowstoneGrpcMetadataEntry` déjà classifiées.
|
||||||
|
|
||||||
|
Aucun nom d'environnement n'est lu dans Transport.
|
||||||
|
|
||||||
|
## 4. Profil PublicNode Mainnet
|
||||||
|
|
||||||
|
La surface publique réauditée le 2026-08-24 confirme Yellowstone gRPC Solana Mainnet et affiche :
|
||||||
|
|
||||||
|
```text
|
||||||
|
solana-yellowstone-grpc.publicnode.com:443
|
||||||
|
```
|
||||||
|
|
||||||
|
`YellowstoneGrpcEndpointUrl` exige une URL `http/https`; le document Config représente donc ce même endpoint TLS comme `https://solana-yellowstone-grpc.publicnode.com:443`.
|
||||||
|
|
||||||
|
Le document committé ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
profile_id = publicnode_mainnet
|
||||||
|
provider = publicnode
|
||||||
|
cluster = mainnet-beta
|
||||||
|
protocol = solana_yellowstone
|
||||||
|
metadata = aucune
|
||||||
|
```
|
||||||
|
|
||||||
|
Le profil générique `mainnet_public` reste distinct et ne reçoit pas implicitement un endpoint PublicNode.
|
||||||
|
|
||||||
|
PublicNode expose également Solana Testnet gRPC, mais le hostname exact n'a pas été obtenu depuis une source suffisamment autoritative/indexable pendant cette tranche. Aucun endpoint supposé n'est versionné. `pre.012` réaudite cette valeur avant tout profil/smoke Testnet.
|
||||||
|
|
||||||
|
## 5. Tests ajoutés/étendus
|
||||||
|
|
||||||
|
La fixture V3 couvre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
mapping gRPC valide
|
||||||
|
merge grpc_defaults + session overrides
|
||||||
|
metadata publique issue de KSP_PUBLIC_*
|
||||||
|
secret_metadata issue de KSP_SECRET_*
|
||||||
|
safe_value avec segment secret redacted
|
||||||
|
rejet public <- secret
|
||||||
|
rejet secret <- public
|
||||||
|
```
|
||||||
|
|
||||||
|
Le config committé couvre le profil `publicnode_mainnet` et vérifie la construction d'un `YellowstoneGrpcTransportSettings` valide sans URL dans son `Debug`.
|
||||||
|
|
||||||
|
Le canari public API inclut les nouveaux accessors sans supprimer les anciens.
|
||||||
|
|
||||||
|
## 6. Consolidation plan/validation
|
||||||
|
|
||||||
|
`docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md` et `docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md` sont réorganisés autour de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
état courant
|
||||||
|
scope/architecture stabilisés
|
||||||
|
matrices protocole encore normatives
|
||||||
|
Config V3
|
||||||
|
PublicNode
|
||||||
|
lifecycle/replay
|
||||||
|
historique compact des gates fermés
|
||||||
|
forecast restant
|
||||||
|
critères de clôture
|
||||||
|
```
|
||||||
|
|
||||||
|
Les longues sections append-only propres à chaque prerelease sont remplacées par une table compacte renvoyant aux deltas immuables. Aucun delta historique n'est modifié.
|
||||||
|
|
||||||
|
Les tableaux Markdown sont reformattés selon la convention JetBrains RustRover utilisée dans le workspace : largeur calculée sur la cellule la plus large et un espace de padding autour du contenu.
|
||||||
|
|
||||||
|
## 7. Documentation Config synchronisée
|
||||||
|
|
||||||
|
La README et l'USAGE de `ksp-config-lib`, ainsi que les commentaires du registre, sont mis à jour pour ne plus présenter `std.transport` comme un document uniquement V2 HTTP + WebSocket.
|
||||||
|
|
||||||
|
La documentation finale spécifique Transport/Yellowstone reste un gate de `pre.012`.
|
||||||
|
|
||||||
|
## 8. Fichiers modifiés/ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
config/std.transport.json
|
||||||
|
config/schemas/std.transport.schema.json
|
||||||
|
crates/ksp-config-lib/README.md
|
||||||
|
crates/ksp-config-lib/USAGE.md
|
||||||
|
crates/ksp-config-lib/src/lib.rs
|
||||||
|
crates/ksp-config-lib/src/registry.rs
|
||||||
|
crates/ksp-config-lib/src/transport.rs
|
||||||
|
crates/ksp-config-lib/tests/public_api.rs
|
||||||
|
crates/ksp-config-lib/unit_tests/transport.rs
|
||||||
|
crates/ksp-config-lib/unit_tests/fixtures_v3/std.transport.json
|
||||||
|
docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md
|
||||||
|
docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md
|
||||||
|
deltas/0.2.9/pre.011.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance Cargo n'est ajoutée ou modifiée.
|
||||||
|
|
||||||
|
## 9. Validation source disponible avant livraison
|
||||||
|
|
||||||
|
Exécuté dans l'environnement de préparation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit: clean
|
||||||
|
|
||||||
|
JSON Schema Draft 2020-12 meta-validation: PASS
|
||||||
|
config/std.transport.json against V3 schema: PASS
|
||||||
|
fixture V2 against V3 schema: PASS
|
||||||
|
fixture V1 against V3 schema: PASS
|
||||||
|
fixture V3 against V3 schema: PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Cargo/Rustfmt ne sont pas disponibles dans l'environnement de préparation. Aucune compilation ou test Cargo n'est donc déclaré réussi pour `pre.011` avant gate opérateur.
|
||||||
|
|
||||||
|
## 10. Gate opérateur requis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Attentes de candidate :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Config unit 113 attendus (110 baseline + 3 V3)
|
||||||
|
Config public API 15 attendus
|
||||||
|
Config ownership 5 attendus
|
||||||
|
Transport unit >= 383
|
||||||
|
Transport public API >= 49
|
||||||
|
Transport completeness >= 43
|
||||||
|
Transport doctests 4
|
||||||
|
workspace dependencies 3
|
||||||
|
Clippy aucun warning
|
||||||
|
workspace PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces valeurs ne deviennent des résultats qu'après exécution opérateur.
|
||||||
|
|
||||||
|
## 11. Suite
|
||||||
|
|
||||||
|
Après fermeture de `pre.011` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.012 PublicNode live + Testnet endpoint re-audit + compliance finale + cargo graphs + docs/prompt suivant
|
||||||
|
rel.001 publication stable stricte
|
||||||
|
```
|
||||||
|
|
||||||
|
Si `pre.012` ne peut pas fermer proprement tous les gates dans une seule tranche, continuer avec `pre.013+` plutôt que compacter artificiellement la clôture.
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user