v0.2.1-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: crates/ksp-config-lib/Cargo.toml
|
||||
# version: 5
|
||||
# version: 6
|
||||
|
||||
[package]
|
||||
name = "ksp-config-lib"
|
||||
@@ -15,5 +15,8 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
jsonschema.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/README.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# ksp-config-lib
|
||||
|
||||
@@ -70,7 +70,7 @@ La dépendance inverse est interdite : `ksp-core-lib`, `ksp-logging-lib` et `ksp
|
||||
|
||||
Config ne possède pas le `LoggingGuard`. L'application ou le service qui orchestre le runtime construit la configuration effective puis possède le lifecycle `ksp_logging_lib::initialize/reinitialize`.
|
||||
|
||||
Tauri et les DTO TS-RS restent hors de cette crate. La future `ksp-app-config-desk` doit rester une frontière applicative mince au-dessus des APIs Config.
|
||||
Tauri et les DTO TS-RS restent hors de cette crate. `ksp-app-config-desk` reste une frontière applicative mince au-dessus des APIs Config et découvre les documents standards via le registre Config sans déplacer leur logique métier dans l'application.
|
||||
|
||||
## Secrets
|
||||
|
||||
|
||||
32
crates/ksp-config-lib/tests/transport_devnet_smoke.rs
Normal file
32
crates/ksp-config-lib/tests/transport_devnet_smoke.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
// file: crates/ksp-config-lib/tests/transport_devnet_smoke.rs
|
||||
// version: 1
|
||||
|
||||
//! Opt-in live Devnet smoke for the Config -> Transport foundation path.
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[ignore = "opt-in live Solana Devnet smoke; performs external network requests"]
|
||||
async fn committed_devnet_transport_profile_reaches_all_foundation_canaries() {
|
||||
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"))
|
||||
.expect("committed Config roots must be valid");
|
||||
let registry = ksp_config_lib::ConfigFileRegistry::defaults().expect("default Config registry must be valid");
|
||||
let engine = ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry);
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load().expect("Config must capture the opt-in smoke environment");
|
||||
let resolved = engine
|
||||
.load_resolved_transport_config(std::option::Option::Some("devnet_public"), &environment)
|
||||
.expect("committed devnet_public Transport profile must resolve");
|
||||
assert_eq!(resolved.profile_id(), "devnet_public");
|
||||
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(resolved.into_settings()).expect("resolved Transport settings must construct the HTTP pool");
|
||||
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
|
||||
let health = pool.get_health(&role).await.expect("Devnet getHealth smoke must succeed");
|
||||
assert_eq!(health, ksp_onchain_transport_lib::SolanaNodeHealth::Healthy);
|
||||
let genesis_hash = pool.get_genesis_hash(&role).await.expect("Devnet getGenesisHash smoke must succeed");
|
||||
assert!(!genesis_hash.as_str().is_empty());
|
||||
let version = pool.get_version(&role).await.expect("Devnet getVersion smoke must succeed");
|
||||
assert!(!version.solana_core().is_empty());
|
||||
let balance = pool
|
||||
.get_balance(&role, &ksp_core_lib::PRGIDPK_SOLANA_SYSTEM, std::option::Option::None)
|
||||
.await
|
||||
.expect("Devnet getBalance smoke must succeed");
|
||||
assert!(balance.context().slot() > 0);
|
||||
}
|
||||
135
crates/ksp-onchain-transport-lib/README.md
Normal file
135
crates/ksp-onchain-transport-lib/README.md
Normal file
@@ -0,0 +1,135 @@
|
||||
<!-- file: crates/ksp-onchain-transport-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# `ksp-onchain-transport-lib`
|
||||
|
||||
`ksp-onchain-transport-lib` est la bibliothèque KSP propriétaire du transport on-chain Solana. Sa première surface est le transport HTTP JSON-RPC ; les extensions WebSocket et gRPC sont introduites séparément lorsque leur release les cible.
|
||||
|
||||
## Responsabilités
|
||||
|
||||
La crate possède :
|
||||
|
||||
- les settings runtime HTTP publics ;
|
||||
- les endpoints nommés et leurs metadata provider/cluster ;
|
||||
- les rôles, capabilities/request kinds et priorités ;
|
||||
- la sélection/fairness/fallback du pool ;
|
||||
- les limites RPS/burst/concurrence et le cooldown ;
|
||||
- les deadlines et timeouts ;
|
||||
- le retry/backoff borné et la règle no-resend après dispatch ambigu ;
|
||||
- les enveloppes JSON-RPC 2.0 et leur validation ;
|
||||
- le registre audité des méthodes Solana HTTP ;
|
||||
- l'exécution générique des méthodes standard supportées ;
|
||||
- les wrappers typés explicitement livrés par KSP ;
|
||||
- les snapshots runtime sûrs ;
|
||||
- l'observabilité Transport via `ksp-logging-lib`.
|
||||
|
||||
La crate ne possède ni documents Config, ni persistence Store, ni modèles Program/métier.
|
||||
|
||||
## Frontières de dépendances
|
||||
|
||||
La direction autorisée est :
|
||||
|
||||
```text
|
||||
ksp-config-lib
|
||||
-> ksp-onchain-transport-lib
|
||||
-> ksp-core-lib
|
||||
-> ksp-logging-lib
|
||||
-> reqwest / tokio / serde
|
||||
```
|
||||
|
||||
La direction inverse est interdite :
|
||||
|
||||
```text
|
||||
ksp-onchain-transport-lib -X-> ksp-config-lib
|
||||
ksp-onchain-transport-lib -X-> Store
|
||||
ksp-onchain-transport-lib -X-> Program
|
||||
ksp-onchain-transport-lib -X-> tracing direct
|
||||
```
|
||||
|
||||
`ksp-config-lib` peut donc charger `std.transport.json` et construire `HttpTransportSettings`, tandis que Transport reste directement utilisable par un consumer qui fournit lui-même ses settings.
|
||||
|
||||
## Surface HTTP standard
|
||||
|
||||
Le registre KSP conserve deux inventaires distincts :
|
||||
|
||||
```text
|
||||
52 méthodes HTTP courantes
|
||||
14 méthodes historiques Deprecated / runtime Removed
|
||||
```
|
||||
|
||||
Le registre porte notamment :
|
||||
|
||||
- catégorie ;
|
||||
- request kind ;
|
||||
- statut documentaire ;
|
||||
- statut runtime ;
|
||||
- forme de requête stable/legacy ;
|
||||
- type d'opération ;
|
||||
- classe de retry ;
|
||||
- remplacement historique éventuel ;
|
||||
- release de couverture typée KSP.
|
||||
|
||||
Les quatre wrappers typés de la foundation sont :
|
||||
|
||||
```text
|
||||
getBalance
|
||||
getGenesisHash
|
||||
getHealth
|
||||
getVersion
|
||||
```
|
||||
|
||||
Les autres méthodes courantes peuvent déjà passer par l'exécuteur JSON-RPC standard générique lorsqu'un consumer fournit explicitement descriptor et paramètres JSON. Cette surface raw/générique **ne vaut pas couverture typée** : les wrappers et DTOs typés restants sont introduits selon la matrice HTTP KSP.
|
||||
|
||||
Les 14 méthodes historiques restent découvrables pour la compliance mais sont `Removed` et ne sont pas simulées comme appelables.
|
||||
|
||||
## Résilience
|
||||
|
||||
L'admission est calculée par couple endpoint/rôle. Le pool applique :
|
||||
|
||||
1. rôle et capability ;
|
||||
2. priorité croissante ;
|
||||
3. round-robin dans le meilleur tier ;
|
||||
4. RPS/burst ;
|
||||
5. concurrence ;
|
||||
6. cooldown rate-limit ;
|
||||
7. fallback vers les pairs puis les tiers inférieurs ;
|
||||
8. deadline commune à l'opération et ses retries.
|
||||
|
||||
Les retries ne sont autorisés que lorsque la metadata de méthode et l'état de dispatch les rendent sûrs. `WriteSubmission / NeverAfterDispatch` interdit tout resend automatique après un dispatch ambigu.
|
||||
|
||||
Les erreurs JSON-RPC applicatives ne sont pas transformées en retries transport génériques.
|
||||
|
||||
## Sécurité et diagnostics
|
||||
|
||||
Les URLs d'endpoint peuvent contenir des credentials. Elles ne sont donc pas exposées par les `Debug`, snapshots ou logs ordinaires.
|
||||
|
||||
Les `reqwest::Error` attachées comme source sont neutralisées avec `without_url()` avant exposition dans le contrat d'erreur KSP.
|
||||
|
||||
Le target de tracing est possédé explicitement par :
|
||||
|
||||
```text
|
||||
src/constants.rs
|
||||
TRACING_TARGET = "ksp-onchain-transport-lib"
|
||||
```
|
||||
|
||||
La configuration Logging de référence conserve un fichier dédié Transport à niveau `info`. Un niveau `debug`/`trace` ciblé peut être réactivé temporairement via Config lors d'un développement ou diagnostic explicite.
|
||||
|
||||
## Tests
|
||||
|
||||
Les tests par défaut sont déterministes et n'exigent pas Internet : fixtures JSON et serveur HTTP local couvrent requêtes, réponses, retry, 429, timeout, redaction et routing.
|
||||
|
||||
Un smoke Devnet live existe côté `ksp-config-lib` afin de tester la chaîne réelle :
|
||||
|
||||
```text
|
||||
Config -> std.transport/devnet_public -> HttpTransportPool
|
||||
-> getHealth/getGenesisHash/getVersion/getBalance
|
||||
```
|
||||
|
||||
Il est `ignored` par défaut et doit être exécuté explicitement.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`USAGE.md`](USAGE.md) — consommation directe, Config -> Transport, API typed/raw et inspection runtime ;
|
||||
- [`../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — plan et matrice HTTP ;
|
||||
- [`../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md`](../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md) — matrice de clôture ;
|
||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — configuration standard HTTP.
|
||||
164
crates/ksp-onchain-transport-lib/USAGE.md
Normal file
164
crates/ksp-onchain-transport-lib/USAGE.md
Normal file
@@ -0,0 +1,164 @@
|
||||
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Utilisation de `ksp-onchain-transport-lib`
|
||||
|
||||
Ce guide présente les surfaces publiques destinées aux consumers. Les notes de release restent dans `CHANGELOG.md` et les deltas.
|
||||
|
||||
## 1. Construction directe du runtime
|
||||
|
||||
Transport peut être utilisé sans Config. Le consumer construit les settings publics puis le pool :
|
||||
|
||||
```rust
|
||||
let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse("https://api.devnet.solana.com") {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
|
||||
ksp_onchain_transport_lib::HttpRoleName::new("default"),
|
||||
true,
|
||||
vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()],
|
||||
100,
|
||||
ksp_onchain_transport_lib::HttpRoleLimits::new(None, None, None, None),
|
||||
);
|
||||
let endpoint = ksp_onchain_transport_lib::HttpEndpointSettings::new(
|
||||
"solana_devnet_public",
|
||||
true,
|
||||
ksp_onchain_transport_lib::HttpProviderName::new("solana-public"),
|
||||
ksp_onchain_transport_lib::HttpClusterName::new("devnet"),
|
||||
url,
|
||||
std::time::Duration::from_secs(5),
|
||||
std::time::Duration::from_secs(15),
|
||||
Some(8),
|
||||
vec![role],
|
||||
);
|
||||
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(
|
||||
vec![endpoint],
|
||||
ksp_onchain_transport_lib::HttpRetrySettings::new(
|
||||
2,
|
||||
std::time::Duration::from_millis(100),
|
||||
std::time::Duration::from_secs(2),
|
||||
),
|
||||
);
|
||||
let pool = match ksp_onchain_transport_lib::HttpTransportPool::new(settings) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
```
|
||||
|
||||
`HttpTransportSettings::validate()` peut être appelé explicitement avant la construction du pool lorsque le consumer veut séparer validation et initialisation.
|
||||
|
||||
## 2. Construction via `ksp-config-lib`
|
||||
|
||||
Lorsque le consumer utilise Config, la direction reste Config -> Transport :
|
||||
|
||||
```rust
|
||||
let resolved = match engine.load_resolved_transport_config(Some("devnet_public"), &environment) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let pool = match ksp_onchain_transport_lib::HttpTransportPool::new(resolved.into_settings()) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
```
|
||||
|
||||
Le document standard peut contenir une URL provenant d'un `KSP_SECRET_*`. La valeur réelle est transmise au runtime, mais les projections sûres et `Debug` restent redacted.
|
||||
|
||||
## 3. Appels typés
|
||||
|
||||
Les wrappers typés se trouvent directement sur `HttpTransportPool`.
|
||||
|
||||
```rust
|
||||
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
|
||||
let health = pool.get_health(&role).await;
|
||||
let genesis_hash = pool.get_genesis_hash(&role).await;
|
||||
let version = pool.get_version(&role).await;
|
||||
let balance = pool
|
||||
.get_balance(
|
||||
&role,
|
||||
&ksp_core_lib::PRGIDPK_SOLANA_SYSTEM,
|
||||
Some(&ksp_onchain_transport_lib::GetBalanceConfig::new(
|
||||
Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
```
|
||||
|
||||
Les types de retour associés sont :
|
||||
|
||||
```text
|
||||
SolanaNodeHealth
|
||||
SolanaGenesisHash
|
||||
SolanaNodeVersion
|
||||
GetBalanceResult
|
||||
SolanaRpcContext
|
||||
```
|
||||
|
||||
`GetBalanceResult::value()` renvoie les lamports et `context()` fournit le slot/API version retournés par Solana.
|
||||
|
||||
## 4. Exécution JSON-RPC standard générique
|
||||
|
||||
Une méthode courante auditée peut être appelée via son descriptor :
|
||||
|
||||
```rust
|
||||
if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getSlot") {
|
||||
let _result = pool.execute_standard_rpc(&role, descriptor, vec![]).await;
|
||||
}
|
||||
```
|
||||
|
||||
Cette API retourne un `serde_json::Value`. Elle est utile pour les consumers techniques et pour préparer les futures surfaces typées, mais elle ne remplace pas le wrapper typé d'une méthode dans la matrice de couverture KSP.
|
||||
|
||||
Avant exécution, `ensure_runtime_supported()` est appliqué. Une méthode historique `Removed` retourne `ERROR_CODE_METHOD_REMOVED` au lieu d'émettre un appel réseau fictif.
|
||||
|
||||
## 5. Sélection et admission sans exécuter la requête
|
||||
|
||||
Pour inspecter le routing :
|
||||
|
||||
```rust
|
||||
if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getBalance") {
|
||||
let _selection = pool.select_for_method(&role, descriptor);
|
||||
let _permit = pool.acquire_for_method(&role, descriptor).await;
|
||||
}
|
||||
```
|
||||
|
||||
Dans le même bloc, `acquire_for_method()` réserve réellement la capacité RPS/concurrence sous deadline.
|
||||
|
||||
`HttpRequestPermit` détient la capacité de concurrence jusqu'à sa destruction. Aucun verrou synchrone n'est conservé pendant l'attente réseau.
|
||||
|
||||
## 6. Snapshots runtime
|
||||
|
||||
`HttpTransportPool::snapshot()` fournit une vue sûre des endpoints/rôles : disponibilité, limites, requêtes en vol, cooldown restant et compteurs runtime.
|
||||
|
||||
Les URLs d'endpoint n'y apparaissent jamais.
|
||||
|
||||
## 7. Retry et write submissions
|
||||
|
||||
La policy de retry est portée par la metadata des méthodes et `evaluate_transport_retry()`.
|
||||
|
||||
Les reads/simulations classés `RetrySafe` peuvent être réessayés dans le budget configuré lorsqu'une cause transport est explicitement retryable.
|
||||
|
||||
Pour une opération `WriteSubmission / NeverAfterDispatch`, un timeout ou autre résultat ambigu après dispatch arrête la resoumission automatique. Le consumer métier ne doit pas contourner cette protection avec une boucle de retry externe aveugle.
|
||||
|
||||
## 8. Logging
|
||||
|
||||
Les événements Transport utilisent le target :
|
||||
|
||||
```text
|
||||
ksp-onchain-transport-lib
|
||||
```
|
||||
|
||||
Ne jamais journaliser l'URL complète, un token provider, un body massif, une transaction complète ou une réponse complète.
|
||||
|
||||
La configuration standard route les événements `info` de Transport vers un fichier dédié. Pour une investigation temporaire, élever uniquement ce target/sink à `debug` ou `trace`, puis revenir à `info` avant clôture du développement.
|
||||
|
||||
## 9. Smoke Devnet opt-in
|
||||
|
||||
Le smoke live est volontairement hors des tests par défaut :
|
||||
|
||||
```bash
|
||||
cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Il charge le profil Config `devnet_public`, construit le pool puis appelle les quatre wrappers typés. Les endpoints publics Solana étant rate-limités et non destinés à la production, un échec réseau externe n'est pas interprété comme un échec déterministe de la suite locale.
|
||||
@@ -0,0 +1,48 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 1
|
||||
|
||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||
|
||||
#[test]
|
||||
fn release_registry_partition_matches_the_audited_http_plan() {
|
||||
let mut foundation = 0_usize;
|
||||
let mut accounts_tokens_cluster = 0_usize;
|
||||
let mut transactions = 0_usize;
|
||||
let mut blocks_economics = 0_usize;
|
||||
let mut historical_in_current = 0_usize;
|
||||
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
|
||||
match descriptor.coverage_release() {
|
||||
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_1 => foundation += 1,
|
||||
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_2 => accounts_tokens_cluster += 1,
|
||||
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3 => transactions += 1,
|
||||
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4 => blocks_economics += 1,
|
||||
ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical => historical_in_current += 1,
|
||||
}
|
||||
}
|
||||
assert_eq!(ksp_onchain_transport_lib::current_http_rpc_methods().len(), 52);
|
||||
assert_eq!(ksp_onchain_transport_lib::historical_http_rpc_methods().len(), 14);
|
||||
assert_eq!(foundation, 4);
|
||||
assert_eq!(accounts_tokens_cluster, 22);
|
||||
assert_eq!(transactions, 11);
|
||||
assert_eq!(blocks_economics, 15);
|
||||
assert_eq!(historical_in_current, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_foundation_canaries_and_historical_statuses_are_exact() {
|
||||
let mut foundation_names = std::vec::Vec::<&str>::new();
|
||||
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
|
||||
if descriptor.coverage_release() == ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_1 {
|
||||
foundation_names.push(descriptor.method());
|
||||
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Supported);
|
||||
}
|
||||
}
|
||||
foundation_names.sort_unstable();
|
||||
assert_eq!(foundation_names, std::vec!["getBalance", "getGenesisHash", "getHealth", "getVersion"]);
|
||||
for descriptor in ksp_onchain_transport_lib::historical_http_rpc_methods() {
|
||||
assert_eq!(descriptor.documentation_status(), ksp_onchain_transport_lib::RpcDocumentationStatus::Deprecated);
|
||||
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Removed);
|
||||
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NotApplicable);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user