v0.2.9-pre.002-fix.001

This commit is contained in:
2026-08-24 10:05:05 +02:00
parent 835de48cb7
commit 61fba107ef
7 changed files with 203 additions and 50 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 238
# version: 239
[workspace]
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"]
[workspace.package]
version = "0.2.9-pre.2"
version = "0.2.9-pre.2.fix.1"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
@@ -15,33 +15,33 @@ publish = false
[workspace.dependencies]
argon2 = { version = "^0.5", default-features = false }
base64 = { version = "^0.23" }
chacha20poly1305 = { version = "^0.11", default-features = false }
chrono = { version = "^0.4", default-features = false }
directories = { version = "^6.0" }
ed25519-dalek = { version = "^3.0", default-features = false }
getrandom = { version = "^0.4", default-features = false }
base64 = { version = "^0.23" }
fs2 = { version = "^0.4" }
futures-util = { version = "^0.3", default-features = false }
serde = { version = "^1.0" }
serde_json = { version = "^1.0" }
getrandom = { version = "^0.4", default-features = false }
jsonschema = { version = "^0.51", default-features = false }
reqwest = { version = "^0.13", default-features = false }
serde = { version = "^1.0" }
serde_json = { version = "^1.0" }
solana-keypair = { version = "^3.1", default-features = false }
solana-pubkey = { version = "^4.3", default-features = false }
tauri = { version = "^2.11" }
tauri-build = { version = "^2.6" }
tauri-plugin-dialog = { version = "^2.7" }
tauri-plugin-tracing = { version = "^0.3" }
tempfile = { version = "^3.27" }
tracing = { version = "^0.1", default-features = false }
tracing-subscriber = { version = "^0.3", default-features = false }
tracing-appender = { version = "^0.2", default-features = false }
tokio = { version = "^1.53", default-features = false }
tokio-tungstenite = { version = "^0.30", default-features = false }
tonic = { version = "^0.14", default-features = false }
yellowstone-grpc-proto = { version = "^12.6", default-features = false }
tempfile = { version = "^3.27" }
chrono = { version = "^0.4", default-features = false }
tauri = { version = "^2.11" }
tauri-build = { version = "^2.6" }
tauri-plugin-dialog = { version = "^2.7" }
tauri-plugin-tracing = { version = "^0.3" }
ts-rs = { version = "^12.0" }
yellowstone-grpc-proto = { version = "^12.6", default-features = false }
zeroize = { version = "^1.9" }
[workspace.lints.rust]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/grpc_channel.rs
// version: 1
// version: 2
/// Prepared Yellowstone gRPC channel owned by KSP Transport.
///
@@ -9,7 +9,7 @@ pub struct YellowstoneGrpcChannel {
endpoint_name: std::string::String,
provider: crate::YellowstoneGrpcProviderName,
cluster: crate::YellowstoneGrpcClusterName,
channel: tonic::transport::Channel,
_channel: tonic::transport::Channel,
}
impl YellowstoneGrpcChannel {
@@ -28,6 +28,19 @@ impl YellowstoneGrpcChannel {
.with_context("endpoint_name", endpoint.name()),
);
}
if tokio::runtime::Handle::try_current().is_err() {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
endpoint_name = endpoint.name(),
provider = endpoint.provider().as_str(),
cluster = endpoint.cluster().as_str(),
"Yellowstone gRPC channel preparation requires an active Tokio runtime"
);
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_GRPC_CHANNEL_FAILED, "Yellowstone gRPC channel requires an active Tokio runtime")
.with_context("endpoint_name", endpoint.name()),
);
}
let tonic_endpoint = match tonic::transport::Endpoint::from_shared(endpoint.url().as_str().to_owned()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
@@ -60,7 +73,7 @@ impl YellowstoneGrpcChannel {
endpoint_name: endpoint.name().to_owned(),
provider: endpoint.provider().clone(),
cluster: endpoint.cluster().clone(),
channel,
_channel: channel,
});
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_channel.rs
// version: 1
// version: 2
fn endpoint(enabled: bool, value: &str) -> crate::YellowstoneGrpcEndpointSettings {
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(value).expect("fixture Yellowstone gRPC URL must parse");
@@ -13,8 +13,8 @@ fn endpoint(enabled: bool, value: &str) -> crate::YellowstoneGrpcEndpointSetting
);
}
#[test]
fn grpc_channel_prepare_is_lazy_safe_and_keeps_tonic_private() {
#[tokio::test(flavor = "current_thread")]
async fn grpc_channel_prepare_is_lazy_safe_and_keeps_tonic_private() {
let endpoint = endpoint(true, "http://127.0.0.1:10000/GRPC-SECRET-CANARY");
let channel = crate::YellowstoneGrpcChannel::prepare(&endpoint);
assert!(channel.is_ok());
@@ -28,10 +28,20 @@ fn grpc_channel_prepare_is_lazy_safe_and_keeps_tonic_private() {
let rendered = format!("{channel:?}");
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
assert!(!rendered.contains("127.0.0.1"));
let _channel_type = std::any::type_name_of_val(&channel.channel);
let _channel_type = std::any::type_name_of_val(&channel._channel);
let _wire_type = std::any::type_name::<yellowstone_grpc_proto::geyser::SubscribeRequest>();
}
#[test]
fn grpc_channel_prepare_requires_active_tokio_runtime_without_panicking() {
let endpoint = endpoint(true, "http://127.0.0.1:10000/GRPC-SECRET-CANARY");
let result = crate::YellowstoneGrpcChannel::prepare(&endpoint);
assert!(result.is_err());
let rendered = format!("{result:?}");
assert!(!rendered.contains("GRPC-SECRET-CANARY"));
assert!(!rendered.contains("127.0.0.1"));
}
#[test]
fn grpc_channel_prepare_rejects_disabled_endpoint_before_network_io() {
let endpoint = endpoint(false, "http://127.0.0.1:10000");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_settings.rs
// version: 1
// version: 2
fn endpoint(name: &str, enabled: bool, url: &str, session: crate::YellowstoneGrpcSessionSettings) -> crate::YellowstoneGrpcEndpointSettings {
let parsed = crate::YellowstoneGrpcEndpointUrl::parse(url).expect("fixture Yellowstone gRPC URL must parse");
@@ -130,14 +130,14 @@ fn grpc_transport_settings_reject_excessive_descriptor_and_endpoint_count() {
let parsed = crate::YellowstoneGrpcEndpointUrl::parse("http://127.0.0.1:10000").expect("fixture Yellowstone gRPC URL must parse");
let endpoints = (0..=super::MAX_GRPC_ENDPOINT_COUNT)
.map(|index| {
crate::YellowstoneGrpcEndpointSettings::new(
return crate::YellowstoneGrpcEndpointSettings::new(
format!("endpoint-{index}"),
true,
crate::YellowstoneGrpcProviderName::new("fixture"),
crate::YellowstoneGrpcClusterName::new("devnet"),
parsed.clone(),
crate::YellowstoneGrpcSessionSettings::default(),
)
);
})
.collect();
assert!(crate::YellowstoneGrpcTransportSettings::new(endpoints).validate().is_err());

View File

@@ -0,0 +1,105 @@
<!-- file: deltas/0.2.9/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.9-pre.002-fix.001` — Clippy + runtime Tokio du channel lazy
## 1. Déclencheur
Le premier gate opérateur de `pre.002` a fourni les preuves suivantes :
```text
cargo fmt --all PASS
audit_rust_workspace_rules.py PASS / clean
cargo check --workspace PASS avec warning channel dead_code
cargo clippy --workspace --all-targets FAIL implicit_return dans grpc_settings test
cargo test -p ksp-onchain-transport-lib FAIL 344/345 : connect_lazy hors runtime Tokio
cargo test -p ksp-core-lib --test workspace_dependencies PASS 3/3
cargo tree Transport / duplicates fournis et inspectés
```
Le test gRPC a révélé que `tonic::transport::Endpoint::connect_lazy()` nécessite un reactor Tokio actif et peut paniquer s'il est appelé depuis un contexte synchrone sans runtime. Ce comportement doit être borné dans la surface KSP au lieu d'être laissé comme précondition implicite Tonic.
## 2. Corrections
### Clippy
La closure qui génère l'inventaire d'endpoints excessif dans `unit_tests/grpc_settings.rs` utilise désormais un `return` explicite, conformément à `clippy::implicit_return = deny`.
### Runtime Tokio
`YellowstoneGrpcChannel::prepare()` vérifie maintenant :
```text
tokio::runtime::Handle::try_current()
```
avant l'appel à `connect_lazy()`. En l'absence de runtime actif, KSP retourne :
```text
onchain_transport/grpc_channel_failed
```
avec uniquement `endpoint_name` comme contexte sûr. L'URL et le message interne Tonic ne sont pas réémis.
Le test positif devient un `#[tokio::test(flavor = "current_thread")]`. Un nouveau test synchrone prouve que l'absence de runtime est retournée comme erreur sans exposer l'URL canari.
### Champ Tonic
Le channel **reste un champ de production**. Il n'est pas placé sous `#[cfg(test)]`, car `pre.002` matérialise réellement la ressource physique N1 qui sera consommée en `pre.003`.
Le champ privé est temporairement nommé :
```text
_channel: tonic::transport::Channel
```
pour exprimer qu'il est intentionnellement conservé mais pas encore consommé par une opération de production dans cette tranche. Cela supprime le warning `dead_code` sans modifier la shape production/test.
## 3. Graphe Cargo observé
Le gate opérateur confirme :
```text
tonic 0.14.6
yellowstone-grpc-proto 12.6.0
prost/prost-types 0.14.4
solana-pubkey 4.3.0 unifié
http 1.5.0
hyper 1.11.0
hyper-util 0.1.20
tower 0.5.3
bytes 1.12.1
```
Les occurrences Prost 0.14.4 dans `--duplicates` viennent des unités runtime/build (`prost-build`, `tonic-prost-build`) de la même version ; aucune seconde version concurrente n'est introduite.
## 4. Version
Le fix touche le code/runtime ; la version workspace devient :
```text
0.2.9-pre.2.fix.1
```
Commit attendu après validation :
```text
v0.2.9-pre.002-fix.001
```
## 5. Validation requise
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test -p ksp-core-lib --test workspace_dependencies
cargo test --workspace
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib --duplicates
cargo tree --duplicates
```
Le passage à `pre.003` est interdit tant que ces gates ne sont pas verts ou qu'un nouvel écart n'est pas traité par un fix supplémentaire.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
> **Statut : `0.2.9-pre.002` — première matérialisation N1 préparée : dépendances Yellowstone/Tonic minimales, settings runtime bornés, error mapping initial et channel Tonic lazy privé. Les gates Cargo restent à exécuter par lopérateur avant commit. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 1520 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
> **Statut : `0.2.9-pre.002-fix.001` — correctif technique du premier gate opérateur : le channel lazy est conservé comme état N1 de production, `prepare()` refuse proprement labsence de runtime Tokio au lieu de laisser Tonic paniquer, et les canaries Clippy/runtime sont corrigés. Les gates Cargo doivent être réexécutés après ce fix avant commit. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 1520 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
## 1. Objet, base et état d'ouverture
@@ -549,6 +549,8 @@ futures-util dépendance existante inchangée
Le choix est volontairement plus étroit que le forecast initial : `pre.002` matérialise les messages wire publiés et le channel HTTP/2 lazy, mais **pas encore le client RPC généré, TLS, metadata ou compression**. Les éventuelles features/dependencies supplémentaires doivent être justifiées par `pre.003` et vérifiées par `cargo tree`; elles ne sont pas activées par anticipation.
Le premier gate opérateur de `pre.002` confirme l'alignement de versions utile : Tonic 0.14.6 réutilise `http` 1.5, `hyper` 1.11, `hyper-util` 0.1, `tower` 0.5 et `bytes` 1.12 déjà présents ; `yellowstone-grpc-proto` unifie `solana-pubkey` en 4.3.0. Les occurrences Prost 0.14.4 visibles dans `cargo tree --duplicates` correspondent aux contextes runtime/build de la même version, notamment `prost-build`/`tonic-prost-build`, et ne constituent pas une seconde génération de version à corriger.
### C. proto/génération KSP minimale bornée
Avantage : contrôle maximum du code généré.
@@ -895,8 +897,8 @@ Prévision courante :
pre.001 DONE — audit upstream/service/proto + providers gratuits + licences/deps + architecture + threat model + sizing
budget : 1520 min nominal ; preuve : plan + matrice + stratégie B + forecast recalibré
pre.002 IMPLEMENTED — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
budget : 1520 min ; preuve locale : audit statique KSP clean + redaction/API canaries ajoutés ; compile/tests/cargo tree = gate opérateur
pre.002 FIX.001 CANDIDATE — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
budget : 1520 min ; premier gate : fmt/audit/check PASS, Clippy/test ont révélé return implicite + runtime Tokio ; fix appliqué ; cargo tree inspecté
pre.003 moteur Yellowstone : TLS/metadata générique + fixture locale + 7 unary RPCs
budget : 1520 min ; preuve : connect/TLS/timeouts/Status safe + wire unary exact

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
> **Statut : matrice ouverte par `0.2.9-pre.001`, corrigée par `pre.001-fix.002` et avancée par `pre.002`. N1 possède désormais une première matérialisation settings/error/channel lazy ; la validation Cargo de cette tranche reste un gate opérateur. Le scope distingue moteur Yellowstone, façade Solana standard et première intégration PublicNode. OrbitFlare et Helius sont les seules releases provider suivantes actuellement planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
> **Statut : matrice ouverte par `0.2.9-pre.001`, corrigée par `pre.001-fix.002`, avancée par `pre.002` puis corrigée par `pre.002-fix.001`. Le premier gate opérateur a validé fmt/audit/check et le graphe, puis a révélé un retour Clippy implicite et l'exigence de runtime Tokio de `connect_lazy()` ; le fix borne désormais ce cas sans panic et attend la réexécution Cargo complète. Le scope distingue moteur Yellowstone, façade Solana standard et première intégration PublicNode. OrbitFlare et Helius sont les seules releases provider suivantes actuellement planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
## 1. Autorités du gate
@@ -402,7 +402,7 @@ Les autres providers restent en TODO/IDEAS sans release dédiée. Aucune façade
```text
pre.001 DONE audit/sizing/architecture 1520 min nominal
pre.002 IMPLEMENTED / CARGO GATE moteur: deps/settings/errors/channel 1520 min
pre.002 FIX.001 CANDIDATE moteur: deps/settings/errors/channel 1520 min
pre.003 TODO moteur: TLS/metadata + fixture + 7 unary 1520 min
pre.004 TODO standard: Subscribe common/from_slot/bounds 1520 min
pre.005 TODO standard: accounts + slots 1520 min
@@ -446,25 +446,48 @@ Gate de sizing : une prerelease estimée à plus de 20 minutes est scindée ; si
**Verdict `pre.001` : gate positif ; `pre.002` peut matérialiser la stratégie B sous validation Cargo.**
## 17. Gate `pre.002`
## 17. Gate `pre.002` / `pre.002-fix.001`
| Critère de tranche | Verdict local |
|---------------------------------------------------------|------------------------------------------------|
| workspace version `0.2.9-pre.2` | PASS source |
| stratégie B matérialisée sans `yellowstone-grpc-client` | PASS source |
| proto `12.6` sans default/features KSP | PASS manifest |
| Tonic `0.14` limité à `channel` | PASS manifest |
| aucun Config/env dans Transport gRPC | PASS source/audit |
| aucun couplage WebSocket | PASS source |
| settings runtime bornés | PASS source/tests ajoutés |
| URL/channel diagnostics redacted | PASS source/tests ajoutés |
| channel Tonic lazy privé | PASS source/API canary ajouté |
| raw Tonic/Yellowstone absent de l'API publique | PASS source/API canary ajouté |
| audit `scripts/audit_rust_workspace_rules.py` | PASS / clean |
| `cargo fmt --all` | PENDING opérateur |
| `cargo check --workspace` | PENDING opérateur |
| `cargo clippy --workspace --all-targets` | PENDING opérateur |
| tests Transport/Core/workspace | PENDING opérateur |
| `cargo tree` direct/duplicates | PENDING opérateur |
Premier passage opérateur sur `pre.002` :
**Verdict `pre.002` local : implémentation candidate prête pour validation opérateur. La tranche n'est déclarée fermée qu'après les gates Cargo réels.**
| Gate / observation | Résultat avant fix | État après `fix.001` |
|---------------------------------------------------------|----------------------------------------------------------------------|---------------------------------------------|
| workspace version | `0.2.9-pre.2` | `0.2.9-pre.2.fix.1` |
| `cargo fmt --all` | PASS | à réexécuter |
| audit Rust workspace | PASS / clean | PASS local / clean |
| `cargo check --workspace` | PASS, avec warning `channel` jamais lu | warning supprimé par état privé `_channel` |
| `cargo clippy --workspace --all-targets` | FAIL : `implicit_return` dans `unit_tests/grpc_settings.rs` | corrigé ; à réexécuter |
| `cargo test -p ksp-onchain-transport-lib` | FAIL : `connect_lazy()` appelé hors reactor Tokio, 344/345 avant fix | test/runtime corrigés ; à réexécuter |
| dependency canary Core | PASS / 3 passed | inchangé |
| `cargo tree -p ksp-onchain-transport-lib` | obtenu et inspecté | PASS graphe |
| `cargo tree -p ... --duplicates` / workspace duplicates | obtenu et inspecté | PASS avec doublons build/runtime documentés |
Correction `fix.001` :
```text
le champ Tonic reste présent en production ; il n'est pas placé sous cfg(test)
le champ privé devient `_channel` tant qu'aucune opération pre.003 ne le consomme
prepare() vérifie tokio::runtime::Handle::try_current() avant connect_lazy()
absence de runtime -> KspError grpc_channel_failed sûr, jamais panic Tonic
le test positif s'exécute sous #[tokio::test]
un test sync couvre explicitement l'absence de runtime et la redaction
la closure de cardinalité settings utilise un return explicite conforme à Clippy
```
Le choix de ne pas mettre le channel sous `#[cfg(test)]` est intentionnel : `pre.002` matérialise une ressource N1 réelle de production qui sera consommée par `pre.003`; une compilation normale sans ce champ rendrait le contrat de tranche artificiellement différent entre production et tests.
Inspection du graphe opérateur :
```text
tonic 0.14.6
yellowstone-grpc-proto 12.6.0
prost/prost-types 0.14.4
solana-pubkey 4.3.0 unifié
http/hyper/hyper-util 1.5.0 / 1.11.0 / 0.1.20
tower 0.5.3
bytes 1.12.1
```
Aucune version concurrente Tonic/Prost/Solana n'a été introduite dans le graphe Transport. Les répétitions Prost de même version proviennent des unités build/runtime de la crate proto publiée.
**Verdict `pre.002-fix.001` : correctif source prêt ; `pre.002` reste ouverte jusqu'à réexécution verte de fmt/audit/check/clippy/tests/workspace et confirmation finale du graphe.**