14 Commits

Author SHA1 Message Date
5b3967327a v0.2.4-rel.001 2026-08-18 23:51:56 +02:00
804b92b957 v0.2.4-pre.009-fix.001 2026-08-18 23:26:17 +02:00
e6772c109d v0.2.4-pre.009 2026-08-18 22:43:09 +02:00
7e84522787 v0.2.4-pre.008-fix.001 2026-08-18 21:53:33 +02:00
37461ec248 v0.2.4-pre.008 2026-08-18 21:52:00 +02:00
4252a21140 v0.2.4-pre.007 2026-08-18 21:33:21 +02:00
7a889dd466 v0.2.4-pre.006 2026-08-18 21:21:02 +02:00
d3cc7c93f0 v0.2.4-pre.005 2026-08-18 21:03:00 +02:00
561b6678ed v0.2.4-pre.004 2026-08-18 20:54:16 +02:00
e500024c3a v0.2.4-pre.003 2026-08-18 20:51:52 +02:00
033800f897 v0.2.4-pre.002-fix.001 2026-08-18 20:21:01 +02:00
cf8d79567d v0.2.4-pre.002 2026-08-18 20:12:46 +02:00
cb19742cf7 v0.2.4-pre.001-fix.001 2026-08-18 19:51:00 +02:00
c03a3eaf5d v0.2.4-pre.001 2026-08-18 19:36:57 +02:00
88 changed files with 10195 additions and 80 deletions

View File

@@ -1,8 +1,12 @@
<!-- file: CHANGELOG.md --> <!-- file: CHANGELOG.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Changelog KSP # Changelog KSP
## 0.2.4 — HTTP Blocks + Economics + compliance HTTP finale — 2026-08-18
`0.2.4` achève la surface HTTP Solana standard de `ksp-onchain-transport-lib` : les 10 wrappers Blocks et 5 wrappers Economics portent la couverture typée à **52/52 méthodes HTTP courantes**, tandis que les **14/14 méthodes historiques Deprecated/runtime Removed** restent conservées pour compliance. La release stabilise notamment `getBlock` moderne avec sa forme bare encoding legacy dépréciée, les quatre variantes `transactionDetails`, les versions transaction numériques génériques, `numRewardPartitions` (SIMD-0118), `commissionBps` (SIMD-0291), les overloads de ranges/performance, `getBlockProduction`, les valeurs Economics fournies par le runtime et `getInflationReward` positionnel sans plafond d'adresses inventé. Le réaudit final confirme l'égalité exacte entre l'inventaire HTTP officiel courant et le registre KSP, applique `KSP-TRANSPORT-007` aux **52/52 wrappers**, et ne matérialise aucune extension spéculative issue de la watchlist SIMD. Les canaries de frontières, le workspace complet et les deux smokes Devnet — Transport pur couvrant Accounts/Tokens/Cluster/Transactions/Blocks/Economics et composition Config -> Transport transitoire — sont validés avant publication. Le prompt `prompts/010-V0_2_5_START_PROMPT.md`, enrichi par `pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` avec threat model, interopérabilité `.kspwallet` et capacités indépendantes VIEW/OWNER.
Ce changelog résume uniquement les releases KSP considérées comme stables, dans l'ordre chronologique décroissant. Les détails de chaque livraison restent dans `deltas/`. Ce changelog résume uniquement les releases KSP considérées comme stables, dans l'ordre chronologique décroissant. Les détails de chaque livraison restent dans `deltas/`.
## 0.2.3 — HTTP Transactions — 2026-08-18 ## 0.2.3 — HTTP Transactions — 2026-08-18

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 131 # version: 142
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"] members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
[workspace.package] [workspace.package]
version = "0.2.3" version = "0.2.4"
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"

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 40 --> <!-- version: 43 -->
# Roadmap KSP # Roadmap KSP
@@ -48,8 +48,8 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
- [X] `0.2.1`**HTTP transport foundation réduite par le gate `pre.001`** : crate/settings/JSON-RPC/registry 52 current + 14 deprecated historiques, pool/rôles/limites/retry, Config adapter, documentation et 4 méthodes typées canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) publiés stables. - [X] `0.2.1`**HTTP transport foundation réduite par le gate `pre.001`** : crate/settings/JSON-RPC/registry 52 current + 14 deprecated historiques, pool/rôles/limites/retry, Config adapter, documentation et 4 méthodes typées canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) publiés stables.
- [X] `0.2.2` — HTTP Accounts + Tokens + Cluster : 22 wrappers typés (5 Accounts + 5 Tokens + 12 Cluster), canaries de complétude 52+14, smoke Devnet Transport pur et smoke historique Config -> Transport validés, documentation durable et prompt `0.2.3` publiés stables. - [X] `0.2.2` — HTTP Accounts + Tokens + Cluster : 22 wrappers typés (5 Accounts + 5 Tokens + 12 Cluster), canaries de complétude 52+14, smoke Devnet Transport pur et smoke historique Config -> Transport validés, documentation durable et prompt `0.2.3` publiés stables.
- [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants. - [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants.
- [ ] `0.2.4` Compléter les 10 méthodes HTTP Blocks + 5 Economics et exécuter la compliance finale de toute la surface HTTP 52 current + 14 deprecated historiques. - [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication.
- [ ] `0.2.5` — Introduire `ksp-wallet-lib`, le format `.kspwallet`, la gestion sûre des secrets et une architecture d'import/export extensible ; exclure `WalletPolicy`. - [ ] `0.2.5` — Introduire `ksp-wallet-lib` et le format interopérable `.kspwallet` avec threat model offline, capacités indépendantes VIEW/OWNER, metadata protégées, signature, rotations de key slots et import/export extensible ; exclure `WalletPolicy`.
- [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP, notamment pour afficher l'identité et le solde d'un wallet. - [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP, notamment pour afficher l'identité et le solde d'un wallet.
- [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe. - [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
- [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client. - [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/README.md --> <!-- file: crates/ksp-onchain-transport-lib/README.md -->
<!-- version: 7 --> <!-- version: 9 -->
# `ksp-onchain-transport-lib` # `ksp-onchain-transport-lib`
@@ -69,21 +69,19 @@ Le registre porte notamment :
- remplacement historique éventuel ; - remplacement historique éventuel ;
- release de couverture typée KSP. - release de couverture typée KSP.
La release stable `0.2.3` porte la surface typée à **37 méthodes courantes** : La release stable `0.2.4` complète la surface typée des **52 méthodes courantes** :
```text ```text
0.2.1 foundation : 4 0.2.1 foundation : 4
0.2.2 Accounts : 5 0.2.2 Accounts/Tokens/Cluster : 22
0.2.2 Tokens : 5
0.2.2 Cluster : 12
0.2.3 Transactions : 11 0.2.3 Transactions : 11
0.2.4 Blocks/Economics : 15
total : 52
``` ```
Les quatre canaris foundation restent `getBalance`, `getGenesisHash`, `getHealth` et `getVersion`. `0.2.2` ajoute les 22 wrappers Accounts/Tokens/Cluster. `0.2.3` ajoute les 11 wrappers Transactions, dont `requestAirdrop` et `sendTransaction` en `WriteSubmission / NeverAfterDispatch` et `simulateTransaction` en `Simulation / RetrySafe`. `0.2.4` ajoute les dix wrappers Blocks et les cinq wrappers Economics. Les points sensibles restent notamment `getBlock` moderne + bare encoding legacy deprecated, les quatre `transactionDetails`, les versions transaction numériques génériques, `numRewardPartitions`, `commissionBps`, les overloads de `getBlocks`, les ranges de production, les valeurs d'inflation/minimum de délégation fournies par le runtime et les `null` positionnels de `getInflationReward`.
Les **15 méthodes Blocks/Economics** encore affectées à `0.2.4` peuvent déjà passer par l'exécuteur JSON-RPC standard générique lorsqu'un consumer fournit explicitement descriptor et paramètres JSON, mais cette surface raw/générique **ne vaut pas couverture typée**. `KSP-TRANSPORT-007` impose qu'un wrapper typé couvre toutes les possibilités RPC supportées retenues par l'audit : paramètres/options, overloads et formes legacy encore supportées, contraintes déterministes utiles et variantes de réponse pertinentes sans perte. Le réaudit final `0.2.4` agrège le réaudit 37/37 de `0.2.3` avec les 15 nouveaux wrappers et porte la preuve stable à **52/52**.
`KSP-TRANSPORT-007` impose qu'un wrapper typé couvre toutes les possibilités RPC supportées retenues par l'audit : paramètres/options, overloads et formes legacy encore supportées, contraintes déterministes utiles et variantes de réponse pertinentes sans perte. Le réaudit `0.2.3-pre.008` confirme cette complétude sur les 37 wrappers courants.
Les 14 méthodes historiques restent découvrables pour la compliance mais sont `Removed` et ne sont pas simulées comme appelables. Les 14 méthodes historiques restent découvrables pour la compliance mais sont `Removed` et ne sont pas simulées comme appelables.
@@ -128,7 +126,9 @@ Deux smokes Devnet opt-in sont séparés par responsabilité :
```text ```text
Transport pur : settings programmatiques -> HttpTransportPool Transport pur : settings programmatiques -> HttpTransportPool
-> Accounts/Tokens/Cluster représentatifs -> Accounts/Tokens/Cluster représentatifs
-> getLatestBlockhash/isBlockhashValid/getTransactionCount -> trois reads Transactions
-> getBlockHeight
-> getInflationRate/getStakeMinimumDelegation
Composition historique : Config -> std.transport/devnet_public -> HttpTransportPool Composition historique : Config -> std.transport/devnet_public -> HttpTransportPool
-> getHealth/getGenesisHash/getVersion/getBalance -> getHealth/getGenesisHash/getVersion/getBalance
@@ -146,4 +146,6 @@ Les deux sont `ignored` par défaut. Le smoke Transport appartient durablement
- [`../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) — matrice finale validée `0.2.2` ; - [`../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) — matrice finale validée `0.2.2` ;
- [`../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de `0.2.3` ; - [`../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de `0.2.3` ;
- [`../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée `0.2.3` ; - [`../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée `0.2.3` ;
- [`../../docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](../../docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan Blocks/Economics et compliance HTTP finale ;
- [`../../docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](../../docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) — matrice finale validée `52/52 + 14/14` et audit `KSP-TRANSPORT-007` global ;
- [`../../config/std.transport.json`](../../config/std.transport.json) — configuration standard HTTP. - [`../../config/std.transport.json`](../../config/std.transport.json) — configuration standard HTTP.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md --> <!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 7 --> <!-- version: 9 -->
# Utilisation de `ksp-onchain-transport-lib` # Utilisation de `ksp-onchain-transport-lib`
@@ -96,7 +96,7 @@ let epoch = pool.get_epoch_info(&role, None).await;
let vote_accounts = pool.get_vote_accounts(&role, None).await; let vote_accounts = pool.get_vote_accounts(&role, None).await;
``` ```
La release stable `0.2.3` contient 37 wrappers typés : 4 foundation + 5 Accounts + 5 Tokens + 12 Cluster + 11 Transactions. Les DTOs Transport conservent les `null`, options et formes wire sans décodage Program/SPL métier. La release stable `0.2.4` contient les **52 wrappers typés courants** : 4 foundation + 22 Accounts/Tokens/Cluster + 11 Transactions + 10 Blocks + 5 Economics. Les DTOs Transport conservent les `null`, options, overloads et formes wire sans décodage Program/SPL métier.
Exemples Transaction représentatifs : Exemples Transaction représentatifs :
@@ -115,6 +115,16 @@ let transaction_count = pool
`getTransaction` expose la config moderne complète et une forme bare-encoding legacy séparée et deprecated. `requestAirdrop` et `sendTransaction` sont des write submissions : elles utilisent la protection centrale `NeverAfterDispatch`. `simulateTransaction` reste une simulation retry-safe et conserve son résultat riche sans introduire de décodage Program. `getTransaction` expose la config moderne complète et une forme bare-encoding legacy séparée et deprecated. `requestAirdrop` et `sendTransaction` sont des write submissions : elles utilisent la protection centrale `NeverAfterDispatch`. `simulateTransaction` reste une simulation retry-safe et conserve son résultat riche sans introduire de décodage Program.
Exemples Blocks/Economics représentatifs :
```rust
let block_height = pool.get_block_height(&role, Some(&context)).await;
let inflation_rate = pool.get_inflation_rate(&role).await;
let stake_minimum = pool.get_stake_minimum_delegation(&role, Some(&context)).await;
```
`getBlock` possède également une forme bare-encoding legacy séparée et deprecated. Les valeurs Economics restent celles du runtime : le consumer ne doit pas supposer localement un taux d'inflation ou un minimum de délégation constant.
## 4. Exécution JSON-RPC standard générique ## 4. Exécution JSON-RPC standard générique
Une méthode courante auditée peut être appelée via son descriptor : Une méthode courante auditée peut être appelée via son descriptor :
@@ -125,7 +135,7 @@ if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getSl
} }
``` ```
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. Cette API retourne un `serde_json::Value`. Elle reste utile pour les extensions provider, les diagnostics et les méthodes hors registre standard, mais elle ne remplace jamais le wrapper typé d'une méthode HTTP standard désormais couverte par 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. 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.
@@ -172,13 +182,13 @@ La configuration standard route les événements `info` de Transport vers un fic
## 9. Smokes Devnet opt-in ## 9. Smokes Devnet opt-in
Le smoke **Transport pur** construit ses settings programmatiquement et exerce un sous-ensemble représentatif de `0.2.2` puis trois reads Transactions de `0.2.3` : Le smoke **Transport pur** construit ses settings programmatiquement et exerce un sous-ensemble représentatif d'Accounts/Tokens/Cluster, trois reads Transactions, puis des reads Blocks/Economics de la release stable `0.2.4` :
```bash ```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
``` ```
Il appelle `getAccountInfo`, `getTokenAccountsByOwner`, `getEpochInfo`, `getVoteAccounts`, puis `getLatestBlockhash`, `isBlockhashValid` et `getTransactionCount`. La branche Token suit la forme Devnet documentée : owner Pubkey ordinaire de l'exemple officiel, selector `programId` avec l'ID canonique du programme SPL Token, puis config explicite `commitment: finalized` + `encoding: jsonParsed`. Une réponse vide reste acceptable. La branche Transaction reste read-only : elle ne déclenche ni airdrop ni soumission de transaction et ne remplace pas les fixtures déterministes couvrant les 11 wrappers. Il appelle `getAccountInfo`, `getTokenAccountsByOwner`, `getEpochInfo`, `getVoteAccounts`, puis `getLatestBlockhash`, `isBlockhashValid`, `getTransactionCount`, `getBlockHeight`, `getInflationRate` et `getStakeMinimumDelegation`. La branche Token suit la forme Devnet documentée : owner Pubkey ordinaire de l'exemple officiel, selector `programId` avec l'ID canonique du programme SPL Token, puis config explicite `commitment: finalized` + `encoding: jsonParsed`. Une réponse vide reste acceptable. La branche Transaction reste read-only : elle ne déclenche ni airdrop ni soumission de transaction et ne remplace pas les fixtures déterministes couvrant les 11 wrappers.
Le smoke historique de **composition Config -> Transport** reste également disponible : Le smoke historique de **composition Config -> Transport** reste également disponible :

View File

@@ -0,0 +1,4 @@
[
{"commitment":[1,2,3,4],"totalStake":1000000000},
{"commitment":null,"totalStake":1000000000}
]

View File

@@ -0,0 +1,7 @@
{
"byIdentity": {
"11111111111111111111111111111111": [8,7],
"Vote111111111111111111111111111111111111111": [4,4]
},
"range": {"firstSlot":430000000,"lastSlot":430000099}
}

View File

@@ -0,0 +1,20 @@
[
{
"previousBlockhash":"previous-a",
"blockhash":"block-a",
"parentSlot":1,
"blockTime":null,
"blockHeight":null
},
{
"previousBlockhash":"previous-b",
"blockhash":"block-b",
"parentSlot":2,
"transactions":null,
"signatures":null,
"rewards":null,
"numRewardPartitions":null,
"blockTime":null,
"blockHeight":null
}
]

View File

@@ -0,0 +1,36 @@
{
"previousBlockhash":"previous-blockhash-fixture",
"blockhash":"blockhash-fixture",
"parentSlot":430000122,
"transactions":[
{
"transaction":{"signatures":["signature-fixture"],"message":{"accountKeys":[],"recentBlockhash":"recent-fixture","instructions":[]}},
"meta":{"err":null,"fee":5000},
"version":1
},
{
"transaction":["AQID","base64"],
"meta":null
}
],
"rewards":[
{
"pubkey":"11111111111111111111111111111111",
"lamports":2500,
"postBalance":1000002500,
"rewardType":"Fee",
"commission":null,
"commissionBps":1234
},
{
"pubkey":"Vote111111111111111111111111111111111111111",
"lamports":1250,
"postBalance":2000001250,
"rewardType":"Voting",
"commission":5
}
],
"numRewardPartitions":4,
"blockTime":1787072400,
"blockHeight":410000000
}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-accounts"],"accountKeys":[{"pubkey":"11111111111111111111111111111111","writable":true,"signer":true,"source":"transaction"}]},"meta":{"err":null,"status":{"Ok":null},"fee":5000,"preBalances":[10],"postBalances":[5]},"version":1}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["3BxsFixtureBase58","base58"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":"3BxsFixtureLegacyBinary","meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32004,"message":"Block not available for slot 430000123"},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32015,"message":"Transaction version (1) is not supported by the requesting client. Please try the request again with maxSupportedTransactionVersion 1"},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-v1"],"message":{"accountKeys":[],"recentBlockhash":"recent-v1","instructions":[],"transactionConfig":{"priorityFee":1,"computeUnitLimit":200000}}},"meta":{"err":null,"fee":5000,"status":{"Ok":null},"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":1,"postBalance":2,"rewardType":"Fee","commission":null,"commissionBps":1234}]},"version":1},{"transaction":["AQID","base64"],"meta":null}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-full-no-rewards"],"message":{"accountKeys":[],"recentBlockhash":"recent-full-no-rewards","instructions":[]}},"meta":{"err":null,"fee":5000,"rewards":null},"version":1}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-json"],"message":{"accountKeys":[],"recentBlockhash":"recent-json","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-parsed"],"message":{"accountKeys":[],"recentBlockhash":"recent-parsed","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":null,"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-b","blockhash":"block-b","parentSlot":2,"transactions":null,"signatures":null,"rewards":null,"numRewardPartitions":null,"blockTime":null,"blockHeight":null},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-a","blockhash":"block-a","parentSlot":1,"blockTime":null,"blockHeight":null},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"signatures":["sig-a","sig-b"]},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"commitment":[1,2,3,4],"totalStake":1000000000},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":410000000,"id":1}

View File

@@ -0,0 +1,11 @@
{
"jsonrpc":"2.0",
"result":{
"context":{"slot":430000123,"apiVersion":"4.2.1"},
"value":{
"byIdentity":{"not-a-pubkey":[8,7]},
"range":{"firstSlot":430000000,"lastSlot":430000099}
}
},
"id":1
}

View File

@@ -0,0 +1,11 @@
{
"jsonrpc":"2.0",
"result":{
"context":{"slot":430000123,"apiVersion":"4.2.1"},
"value":{
"byIdentity":{"11111111111111111111111111111111":[8,7]},
"range":{"firstSlot":430000000,"lastSlot":430000099}
}
},
"id":1
}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":null,"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":1787072400,"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[430000100,430000103,430000109],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[430000200,430000201,430000205],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":250000,"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"initial":0.07123456789,"terminal":0.0123456789,"taper":0.2718281828,"foundation":0.0042,"foundationTerm":6.25},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"total":0.0387654321,"validator":0.037654321,"foundation":0.0011111111,"epoch":913},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[{"epoch":912,"effectiveSlot":431500010,"amount":777,"postBalance":1000777,"commission":5,"commissionBps":500},null,{"epoch":912,"effectiveSlot":431500010,"amount":777,"postBalance":1000777,"commission":5,"commissionBps":500}],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32017,"message":"Epoch rewards period still active at slot 431500000","data":{"slot":431500000,"currentBlockHeight":400000000,"rewardsCompleteBlockHeight":400000100}},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32016,"message":"Minimum context slot has not been reached","data":{"contextSlot":431000000}},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[null],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[{"epoch":912,"effectiveSlot":431500001,"amount":2500000,"postBalance":1002500000,"commission":7,"commissionBps":725},null,{"epoch":912,"effectiveSlot":431500003,"amount":1250000,"postBalance":501250000,"commission":null},{"epoch":912,"effectiveSlot":431500004,"amount":500000,"postBalance":200500000,"commission":3,"commissionBps":null}],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":[{"slot":430000123,"numTransactions":250000,"numNonVoteTransactions":175000,"numSlots":120,"samplePeriodSecs":60},{"slot":430000000,"numTransactions":200000,"numSlots":110,"samplePeriodSecs":60}],"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32016,"message":"Minimum context slot has not been reached","data":{"contextSlot":430999999}},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"context":{"slot":431000123,"apiVersion":"4.2.1"},"value":42424242},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","error":{"code":-32012,"message":"scan failed"},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"context":{"slot":431000125,"apiVersion":"4.2.1"},"value":{"total":610000000000000000,"circulating":510000000000000000,"nonCirculating":100000000000000000,"nonCirculatingAccounts":[]}},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"context":{"slot":431000124,"apiVersion":"4.2.1"},"value":{"total":610000000000000000,"circulating":510000000000000000,"nonCirculating":100000000000000000,"nonCirculatingAccounts":["11111111111111111111111111111111","Vote111111111111111111111111111111111111111"]}},"id":1}

View File

@@ -0,0 +1 @@
{"initial":0.08,"terminal":0.015,"taper":0.15,"foundation":0.05,"foundationTerm":7.0}

View File

@@ -0,0 +1 @@
{"total":0.042,"validator":0.041,"foundation":0.001,"epoch":812}

View File

@@ -0,0 +1,5 @@
[
{"epoch":811,"effectiveSlot":429900000,"amount":2500000,"postBalance":1002500000,"commission":7,"commissionBps":725},
null,
{"epoch":811,"effectiveSlot":429900000,"amount":1250000,"postBalance":501250000,"commission":null}
]

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":123456,"id":1}

View File

@@ -0,0 +1,4 @@
[
{"slot":430000123,"numTransactions":250000,"numNonVoteTransactions":175000,"numSlots":120,"samplePeriodSecs":60},
{"slot":430000003,"numTransactions":240000,"numSlots":118,"samplePeriodSecs":60}
]

View File

@@ -0,0 +1,9 @@
{
"total":600000000000000000,
"circulating":500000000000000000,
"nonCirculating":100000000000000000,
"nonCirculatingAccounts":[
"11111111111111111111111111111111",
"Vote111111111111111111111111111111111111111"
]
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs // file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 16 // version: 19
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
@@ -12,7 +12,9 @@
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC //! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
//! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives and all eleven Transaction wrappers through //! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives and all eleven Transaction wrappers through
//! `pre.007`: eight reads, two write submissions with centralized no-resend protection, and retry-safe `simulateTransaction`, including complete //! `pre.007`: eight reads, two write submissions with centralized no-resend protection, and retry-safe `simulateTransaction`, including complete
//! modern/legacy `getTransaction` coverage. The `0.2.4` family remains staged. //! modern/legacy `getTransaction` coverage. `0.2.4` completes the HTTP surface with all ten Blocks and five Economics wrappers, including complete
//! modern/legacy `getBlock`, positional inflation rewards, runtime-provided economics values and the final `KSP-TRANSPORT-007` compliance target.
//! The candidate surface therefore exposes typed wrappers for all 52 current audited Solana HTTP methods while retaining 14 removed historical descriptors.
mod client; mod client;
mod constants; mod constants;
@@ -22,9 +24,11 @@ mod json_rpc;
mod pool; mod pool;
mod resilience; mod resilience;
mod rpc_accounts; mod rpc_accounts;
mod rpc_blocks;
mod rpc_canary; mod rpc_canary;
mod rpc_cluster; mod rpc_cluster;
mod rpc_common; mod rpc_common;
mod rpc_economics;
mod rpc_method; mod rpc_method;
mod rpc_tokens; mod rpc_tokens;
mod rpc_transactions; mod rpc_transactions;
@@ -126,6 +130,28 @@ pub use self::rpc_accounts::SolanaProgramAccountFilter;
pub use self::rpc_accounts::SolanaProgramAccountsConfig; pub use self::rpc_accounts::SolanaProgramAccountsConfig;
/// Result union returned by `getProgramAccounts` with or without an RPC context. /// Result union returned by `getProgramAccounts` with or without an RPC context.
pub use self::rpc_accounts::SolanaProgramAccountsResult; pub use self::rpc_accounts::SolanaProgramAccountsResult;
/// Commitment distribution returned by `getBlockCommitment`.
pub use self::rpc_blocks::SolanaBlockCommitment;
/// Block-production counts returned by `getBlockProduction` before the shared RPC context wrapper is applied.
pub use self::rpc_blocks::SolanaBlockProduction;
/// Optional configuration accepted by `getBlockProduction`.
pub use self::rpc_blocks::SolanaBlockProductionConfig;
/// Slot range accepted inside `getBlockProduction` configuration.
pub use self::rpc_blocks::SolanaBlockProductionRange;
/// Effective range reported inside `getBlockProduction` results.
pub use self::rpc_blocks::SolanaBlockProductionResultRange;
/// One reward entry returned in a confirmed block.
pub use self::rpc_blocks::SolanaBlockReward;
/// One transaction entry returned inside a confirmed block.
pub use self::rpc_blocks::SolanaBlockTransaction;
/// Confirmed block wire result returned by `getBlock` when the RPC result is non-null.
pub use self::rpc_blocks::SolanaConfirmedBlock;
/// Modern configuration object accepted by `getBlock`.
pub use self::rpc_blocks::SolanaGetBlockConfig;
/// One entry returned by `getRecentPerformanceSamples`.
pub use self::rpc_blocks::SolanaPerformanceSample;
/// Transaction detail level accepted by modern `getBlock` requests.
pub use self::rpc_blocks::SolanaTransactionDetails;
/// Optional typed configuration for the `getBalance` canary. /// Optional typed configuration for the `getBalance` canary.
pub use self::rpc_canary::GetBalanceConfig; pub use self::rpc_canary::GetBalanceConfig;
/// Typed lamport balance returned by the `getBalance` canary. /// Typed lamport balance returned by the `getBalance` canary.
@@ -172,6 +198,18 @@ pub use self::rpc_common::SolanaRpcResponse;
pub(crate) use self::rpc_common::decode_wire_json; pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters. /// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
pub(crate) use self::rpc_common::parse_wire_pubkey; pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Inflation-governor values returned by `getInflationGovernor`.
pub use self::rpc_economics::SolanaInflationGovernor;
/// Current inflation-rate values returned by `getInflationRate`.
pub use self::rpc_economics::SolanaInflationRate;
/// One non-null positional reward returned by `getInflationReward`.
pub use self::rpc_economics::SolanaInflationReward;
/// Optional epoch/context configuration accepted by `getInflationReward`.
pub use self::rpc_economics::SolanaInflationRewardConfig;
/// Supply totals returned inside the contextual `getSupply` response.
pub use self::rpc_economics::SolanaSupply;
/// Optional configuration accepted by `getSupply`.
pub use self::rpc_economics::SolanaSupplyConfig;
/// Functional category used by the audited Solana HTTP JSON-RPC registry. /// Functional category used by the audited Solana HTTP JSON-RPC registry.
pub use self::rpc_method::HttpRpcCategory; pub use self::rpc_method::HttpRpcCategory;
/// Release that owns typed KSP coverage for one audited HTTP RPC method. /// Release that owns typed KSP coverage for one audited HTTP RPC method.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,591 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_economics.rs
// version: 5
/// Inflation-governor values returned by `getInflationGovernor`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SolanaInflationGovernor {
initial: f64,
terminal: f64,
taper: f64,
foundation: f64,
foundation_term: f64,
}
impl SolanaInflationGovernor {
/// Returns the initial inflation rate.
#[must_use]
pub const fn initial(&self) -> f64 {
return self.initial;
}
/// Returns the terminal inflation rate.
#[must_use]
pub const fn terminal(&self) -> f64 {
return self.terminal;
}
/// Returns the taper/disinflation parameter reported by the runtime.
#[must_use]
pub const fn taper(&self) -> f64 {
return self.taper;
}
/// Returns the foundation allocation rate.
#[must_use]
pub const fn foundation(&self) -> f64 {
return self.foundation;
}
/// Returns the foundation allocation term.
#[must_use]
pub const fn foundation_term(&self) -> f64 {
return self.foundation_term;
}
/// Decodes an inflation-governor result from its Solana JSON wire shape.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireInflationGovernor>(method, value);
return match decoded {
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
initial: wire.initial,
terminal: wire.terminal,
taper: wire.taper,
foundation: wire.foundation,
foundation_term: wire.foundation_term,
}),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Current inflation-rate values returned by `getInflationRate`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SolanaInflationRate {
total: f64,
validator: f64,
foundation: f64,
epoch: u64,
}
impl SolanaInflationRate {
/// Returns the total inflation rate.
#[must_use]
pub const fn total(&self) -> f64 {
return self.total;
}
/// Returns the validator inflation rate.
#[must_use]
pub const fn validator(&self) -> f64 {
return self.validator;
}
/// Returns the foundation inflation rate.
#[must_use]
pub const fn foundation(&self) -> f64 {
return self.foundation;
}
/// Returns the epoch associated with the rate.
#[must_use]
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
/// Decodes an inflation-rate result from its Solana JSON wire shape.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireInflationRate>(method, value);
return match decoded {
std::result::Result::Ok(wire) => {
std::result::Result::Ok(Self { total: wire.total, validator: wire.validator, foundation: wire.foundation, epoch: wire.epoch })
},
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Optional epoch/context configuration accepted by `getInflationReward`.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SolanaInflationRewardConfig {
epoch: std::option::Option<u64>,
commitment: std::option::Option<crate::SolanaCommitment>,
min_context_slot: std::option::Option<u64>,
}
impl SolanaInflationRewardConfig {
/// Creates an inflation-reward configuration.
#[must_use]
pub const fn new(
epoch: std::option::Option<u64>,
commitment: std::option::Option<crate::SolanaCommitment>,
min_context_slot: std::option::Option<u64>,
) -> Self {
return Self { epoch, commitment, min_context_slot };
}
/// Returns the optional target epoch.
#[must_use]
pub const fn epoch(&self) -> std::option::Option<u64> {
return self.epoch;
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns the optional minimum context slot.
#[must_use]
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
return self.min_context_slot;
}
/// Returns whether this config would serialize to an empty object.
pub(crate) const fn is_empty(&self) -> bool {
return self.epoch.is_none() && self.commitment.is_none() && self.min_context_slot.is_none();
}
/// Serializes this config to the exact Solana JSON-RPC object.
#[must_use]
pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(epoch) = self.epoch {
object.insert("epoch".to_owned(), serde_json::Value::Number(epoch.into()));
}
if let std::option::Option::Some(commitment) = self.commitment {
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
}
if let std::option::Option::Some(min_context_slot) = self.min_context_slot {
object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into()));
}
return serde_json::Value::Object(object);
}
}
/// One non-null positional reward returned by `getInflationReward`.
#[derive(Clone, Debug, PartialEq)]
pub struct SolanaInflationReward {
epoch: u64,
effective_slot: u64,
amount: u64,
post_balance: u64,
commission: std::option::Option<u8>,
commission_bps: crate::SolanaWireField<u16>,
}
impl SolanaInflationReward {
/// Returns the rewarded epoch.
#[must_use]
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
/// Returns the slot at which the reward became effective.
#[must_use]
pub const fn effective_slot(&self) -> u64 {
return self.effective_slot;
}
/// Returns the reward amount in lamports.
#[must_use]
pub const fn amount(&self) -> u64 {
return self.amount;
}
/// Returns the post-reward account balance in lamports.
#[must_use]
pub const fn post_balance(&self) -> u64 {
return self.post_balance;
}
/// Returns the nullable legacy/effective commission percentage.
#[must_use]
pub const fn commission(&self) -> std::option::Option<u8> {
return self.commission;
}
/// Returns the basis-point commission while preserving omitted/null/present wire states.
#[must_use]
pub const fn commission_bps(&self) -> &crate::SolanaWireField<u16> {
return &self.commission_bps;
}
/// Decodes one non-null inflation reward from its Solana JSON wire shape.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireInflationReward>(method, value);
return match decoded {
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
epoch: wire.epoch,
effective_slot: wire.effective_slot,
amount: wire.amount,
post_balance: wire.post_balance,
commission: wire.commission,
commission_bps: wire.commission_bps,
}),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Optional configuration accepted by `getSupply`.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SolanaSupplyConfig {
commitment: std::option::Option<crate::SolanaCommitment>,
exclude_non_circulating_accounts_list: std::option::Option<bool>,
}
impl SolanaSupplyConfig {
/// Creates a supply configuration.
#[must_use]
pub const fn new(commitment: std::option::Option<crate::SolanaCommitment>, exclude_non_circulating_accounts_list: std::option::Option<bool>) -> Self {
return Self { commitment, exclude_non_circulating_accounts_list };
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns whether the request explicitly controls omission of the non-circulating account list.
#[must_use]
pub const fn exclude_non_circulating_accounts_list(&self) -> std::option::Option<bool> {
return self.exclude_non_circulating_accounts_list;
}
/// Returns whether this config would serialize to an empty object.
pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.exclude_non_circulating_accounts_list.is_none();
}
/// Serializes this config to the exact Solana JSON-RPC object.
#[must_use]
pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment {
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
}
if let std::option::Option::Some(exclude) = self.exclude_non_circulating_accounts_list {
object.insert("excludeNonCirculatingAccountsList".to_owned(), serde_json::Value::Bool(exclude));
}
return serde_json::Value::Object(object);
}
}
/// Supply totals returned inside the contextual `getSupply` response.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaSupply {
total: u64,
circulating: u64,
non_circulating: u64,
non_circulating_accounts: std::vec::Vec<ksp_core_lib::Pubkey>,
}
impl SolanaSupply {
/// Returns the total supply in lamports.
#[must_use]
pub const fn total(&self) -> u64 {
return self.total;
}
/// Returns the circulating supply in lamports.
#[must_use]
pub const fn circulating(&self) -> u64 {
return self.circulating;
}
/// Returns the non-circulating supply in lamports.
#[must_use]
pub const fn non_circulating(&self) -> u64 {
return self.non_circulating;
}
/// Returns the ordered non-circulating account list supplied by the runtime.
#[must_use]
pub fn non_circulating_accounts(&self) -> &[ksp_core_lib::Pubkey] {
return self.non_circulating_accounts.as_slice();
}
/// Decodes a supply result from its Solana JSON wire shape.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireSupply>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut accounts = std::vec::Vec::with_capacity(wire.non_circulating_accounts.len());
for account in wire.non_circulating_accounts {
let parsed = crate::parse_wire_pubkey(method, "nonCirculatingAccounts", account.as_str());
match parsed {
std::result::Result::Ok(pubkey) => accounts.push(pubkey),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(Self {
total: wire.total,
circulating: wire.circulating,
non_circulating: wire.non_circulating,
non_circulating_accounts: accounts,
});
}
}
impl crate::HttpTransportPool {
/// Executes typed `getInflationGovernor` and returns the runtime-provided schedule values unchanged.
pub async fn get_inflation_governor(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaCommitmentConfig>,
) -> ksp_core_lib::Result<crate::SolanaInflationGovernor> {
let mut params = std::vec::Vec::new();
push_economics_commitment_config(&mut params, config);
let value = self.execute_economics_rpc("getInflationGovernor", role, params).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaInflationGovernor::decode_wire("getInflationGovernor", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getInflationRate` without locally recalculating the inflation schedule.
pub async fn get_inflation_rate(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<crate::SolanaInflationRate> {
let value = self.execute_economics_rpc("getInflationRate", role, std::vec::Vec::new()).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaInflationRate::decode_wire("getInflationRate", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getInflationReward`, preserving input order, positional nulls and runtime commission extensions.
pub async fn get_inflation_reward(
&self,
role: &crate::HttpRoleName,
addresses: &[ksp_core_lib::Pubkey],
config: std::option::Option<&crate::SolanaInflationRewardConfig>,
) -> ksp_core_lib::Result<std::vec::Vec<std::option::Option<crate::SolanaInflationReward>>> {
if let std::option::Option::Some(config) = config
&& config.commitment() == std::option::Option::Some(crate::SolanaCommitment::Processed)
{
return std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
"getInflationReward commitment must be confirmed or finalized when explicitly provided",
)
.with_context("rpc_method", "getInflationReward")
.with_context("commitment", "processed"),
);
}
let address_values = addresses.iter().map(|address| return serde_json::Value::String(address.to_string())).collect::<std::vec::Vec<_>>();
let mut params = std::vec![serde_json::Value::Array(address_values)];
if let std::option::Option::Some(config) = config
&& !config.is_empty()
{
params.push((*config).to_json_value());
}
let value = self.execute_economics_rpc("getInflationReward", role, params).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_inflation_rewards("getInflationReward", value, addresses.len());
}
/// Executes typed `getStakeMinimumDelegation` and preserves the contextual runtime value in lamports.
pub async fn get_stake_minimum_delegation(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaContextConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<u64>> {
let mut params = std::vec::Vec::new();
push_economics_context_config(&mut params, config);
let value = self.execute_economics_rpc("getStakeMinimumDelegation", role, params).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_economics_u64_response("getStakeMinimumDelegation", value);
}
/// Executes typed `getSupply`, preserving the explicit account-list exclusion flag and contextual totals.
pub async fn get_supply(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaSupplyConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaSupply>> {
let mut params = std::vec::Vec::new();
if let std::option::Option::Some(config) = config
&& !config.is_empty()
{
params.push((*config).to_json_value());
}
let value = self.execute_economics_rpc("getSupply", role, params).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_supply_response("getSupply", value);
}
async fn execute_economics_rpc(
&self,
method_name: &'static str,
role: &crate::HttpRoleName,
params: std::vec::Vec<serde_json::Value>,
) -> ksp_core_lib::Result<serde_json::Value> {
let descriptor = economics_descriptor(method_name);
let descriptor = match descriptor {
std::result::Result::Ok(descriptor) => descriptor,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return self.execute_standard_rpc(role, descriptor, params).await;
}
}
fn push_economics_commitment_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaCommitmentConfig>) {
if let std::option::Option::Some(config) = config
&& config.commitment().is_some()
{
params.push((*config).to_json_value());
}
return;
}
fn push_economics_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
if let std::option::Option::Some(config) = config
&& (config.commitment().is_some() || config.min_context_slot().is_some())
{
params.push((*config).to_json_value());
}
return;
}
fn decode_inflation_rewards(
method: &str,
value: serde_json::Value,
expected_count: usize,
) -> ksp_core_lib::Result<std::vec::Vec<std::option::Option<crate::SolanaInflationReward>>> {
let decoded = crate::decode_wire_json::<std::vec::Vec<std::option::Option<serde_json::Value>>>(method, value);
let values = match decoded {
std::result::Result::Ok(values) => values,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if values.len() != expected_count {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "getInflationReward result count does not match the requested address count")
.with_context("rpc_method", method)
.with_context("expected_count", expected_count.to_string())
.with_context("actual_count", values.len().to_string()),
);
}
let mut rewards = std::vec::Vec::with_capacity(values.len());
for value in values {
match value {
std::option::Option::Some(value) => {
let reward = crate::SolanaInflationReward::decode_wire(method, value);
match reward {
std::result::Result::Ok(reward) => rewards.push(std::option::Option::Some(reward)),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
},
std::option::Option::None => rewards.push(std::option::Option::None),
}
}
return std::result::Result::Ok(rewards);
}
fn decode_economics_u64_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<u64>> {
let decoded = crate::decode_wire_json::<WireEconomicsRpcResponse<u64>>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
return match context {
std::result::Result::Ok(context) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, wire.value)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
fn decode_supply_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaSupply>> {
let decoded = crate::decode_wire_json::<WireEconomicsRpcResponse<serde_json::Value>>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
let context = match context {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let supply = crate::SolanaSupply::decode_wire(method, wire.value);
return match supply {
std::result::Result::Ok(supply) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, supply)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
fn economics_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpRpcMethodDescriptor> {
let descriptor = crate::find_http_rpc_method(method);
return match descriptor {
std::option::Option::Some(descriptor)
if descriptor.category() == crate::HttpRpcCategory::Economics && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_4 =>
{
std::result::Result::Ok(descriptor)
},
_ => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Economics descriptor is missing from the audited 0.2.4 registry")
.with_context("rpc_method", method),
),
};
}
#[derive(serde::Deserialize)]
struct WireEconomicsRpcResponse<T> {
context: serde_json::Value,
value: T,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireInflationGovernor {
initial: f64,
terminal: f64,
taper: f64,
foundation: f64,
foundation_term: f64,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireInflationRate {
total: f64,
validator: f64,
foundation: f64,
epoch: u64,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireInflationReward {
epoch: u64,
effective_slot: u64,
amount: u64,
post_balance: u64,
#[serde(default)]
commission: std::option::Option<u8>,
#[serde(default)]
commission_bps: crate::SolanaWireField<u16>,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireSupply {
total: u64,
circulating: u64,
non_circulating: u64,
non_circulating_accounts: std::vec::Vec<std::string::String>,
}
#[cfg(test)]
#[path = "../unit_tests/rpc_economics.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs // file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
// version: 7 // version: 8
/// Binary encoding accepted for serialized transaction input payloads. /// Binary encoding accepted for serialized transaction input payloads.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -838,7 +838,8 @@ pub enum SolanaTransactionVersion {
} }
impl SolanaTransactionVersion { impl SolanaTransactionVersion {
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { /// Decodes a transaction version from the shared Solana JSON wire representation.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
return match value { return match value {
serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy), serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy),
serde_json::Value::Number(value) => { serde_json::Value::Number(value) => {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs // file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 15 // version: 23
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract. //! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -347,3 +347,178 @@ fn public_transaction_pre_007_simulation_wrapper_and_complete_config_are_availab
std::option::Option::Some(ksp_onchain_transport_lib::SolanaAccountEncoding::Base64Zstd), std::option::Option::Some(ksp_onchain_transport_lib::SolanaAccountEncoding::Base64Zstd),
); );
} }
#[test]
fn public_v0_2_4_pre_002_blocks_economics_types_are_available_from_crate_root() {
let block = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::Json),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
assert_eq!(block.max_supported_transaction_version(), std::option::Option::Some(1));
let range = ksp_onchain_transport_lib::SolanaBlockProductionRange::new(100, std::option::Option::Some(200));
assert_eq!(range.last_slot(), std::option::Option::Some(200));
let production_config = ksp_onchain_transport_lib::SolanaBlockProductionConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
std::option::Option::None,
std::option::Option::Some(range),
);
assert_eq!(production_config.range().map(ksp_onchain_transport_lib::SolanaBlockProductionRange::first_slot), std::option::Option::Some(100));
let inflation = ksp_onchain_transport_lib::SolanaInflationRewardConfig::new(
std::option::Option::Some(812),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
std::option::Option::Some(430_000_000),
);
assert_eq!(inflation.epoch(), std::option::Option::Some(812));
let supply = ksp_onchain_transport_lib::SolanaSupplyConfig::new(std::option::Option::None, std::option::Option::Some(false));
assert_eq!(supply.exclude_non_circulating_accounts_list(), std::option::Option::Some(false));
let _block_commitment: std::option::Option<ksp_onchain_transport_lib::SolanaBlockCommitment> = std::option::Option::None;
let _block_production: std::option::Option<ksp_onchain_transport_lib::SolanaBlockProduction> = std::option::Option::None;
let _block_production_result_range: std::option::Option<ksp_onchain_transport_lib::SolanaBlockProductionResultRange> = std::option::Option::None;
let _block_reward: std::option::Option<ksp_onchain_transport_lib::SolanaBlockReward> = std::option::Option::None;
let _block_transaction: std::option::Option<ksp_onchain_transport_lib::SolanaBlockTransaction> = std::option::Option::None;
let _confirmed_block: std::option::Option<ksp_onchain_transport_lib::SolanaConfirmedBlock> = std::option::Option::None;
let _performance_sample: std::option::Option<ksp_onchain_transport_lib::SolanaPerformanceSample> = std::option::Option::None;
let _governor: std::option::Option<ksp_onchain_transport_lib::SolanaInflationGovernor> = std::option::Option::None;
let _rate: std::option::Option<ksp_onchain_transport_lib::SolanaInflationRate> = std::option::Option::None;
let _reward: std::option::Option<ksp_onchain_transport_lib::SolanaInflationReward> = std::option::Option::None;
let _supply: std::option::Option<ksp_onchain_transport_lib::SolanaSupply> = std::option::Option::None;
}
#[test]
fn public_v0_2_4_pre_003_simple_block_wrappers_are_available_from_crate_root() {
let _get_block_commitment = ksp_onchain_transport_lib::HttpTransportPool::get_block_commitment;
let _get_block_height = ksp_onchain_transport_lib::HttpTransportPool::get_block_height;
let _get_block_time = ksp_onchain_transport_lib::HttpTransportPool::get_block_time;
let _get_first_available_block = ksp_onchain_transport_lib::HttpTransportPool::get_first_available_block;
let _minimum_ledger_slot = ksp_onchain_transport_lib::HttpTransportPool::minimum_ledger_slot;
}
#[test]
fn public_v0_2_4_pre_004_range_and_performance_wrappers_are_available_from_crate_root() {
let _get_blocks = ksp_onchain_transport_lib::HttpTransportPool::get_blocks;
let _get_blocks_with_limit = ksp_onchain_transport_lib::HttpTransportPool::get_blocks_with_limit;
let _get_recent_performance_samples = ksp_onchain_transport_lib::HttpTransportPool::get_recent_performance_samples;
}
#[test]
fn public_v0_2_4_pre_005_block_production_wrapper_is_available_from_crate_root() {
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("public fixture identity must parse");
let range = ksp_onchain_transport_lib::SolanaBlockProductionRange::new(10, std::option::Option::Some(20));
let config = ksp_onchain_transport_lib::SolanaBlockProductionConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
std::option::Option::Some(identity),
std::option::Option::Some(range),
);
assert_eq!(config.identity(), std::option::Option::Some(&identity));
assert_eq!(config.range().expect("public range must exist").last_slot(), std::option::Option::Some(20));
let _method = ksp_onchain_transport_lib::HttpTransportPool::get_block_production;
}
#[allow(deprecated)]
#[test]
fn public_v0_2_4_pre_006_get_block_complete_request_forms_are_available_from_crate_root() {
let _get_block = ksp_onchain_transport_lib::HttpTransportPool::get_block;
let _get_block_legacy = ksp_onchain_transport_lib::HttpTransportPool::get_block_legacy;
let config = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Accounts),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
assert_eq!(config.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
assert_eq!(config.encoding(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed));
assert_eq!(config.transaction_details(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Accounts));
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(1));
assert_eq!(config.rewards(), std::option::Option::Some(true));
}
#[test]
fn public_v0_2_4_pre_007_simple_economics_wrappers_are_available_from_crate_root() {
let _get_inflation_governor = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_governor;
let _get_inflation_rate = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_rate;
let _get_stake_minimum_delegation = ksp_onchain_transport_lib::HttpTransportPool::get_stake_minimum_delegation;
let _get_supply = ksp_onchain_transport_lib::HttpTransportPool::get_supply;
let supply_config = ksp_onchain_transport_lib::SolanaSupplyConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
std::option::Option::Some(false),
);
assert_eq!(supply_config.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed));
assert_eq!(supply_config.exclude_non_circulating_accounts_list(), std::option::Option::Some(false));
}
#[test]
fn public_v0_2_4_pre_008_inflation_reward_wrapper_is_available_from_crate_root() {
let _get_inflation_reward = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_reward;
let config = ksp_onchain_transport_lib::SolanaInflationRewardConfig::new(
std::option::Option::Some(912),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
std::option::Option::Some(431_000_000),
);
assert_eq!(config.epoch(), std::option::Option::Some(912));
assert_eq!(config.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
assert_eq!(config.min_context_slot(), std::option::Option::Some(431_000_000));
}
#[allow(deprecated)]
#[test]
fn public_v0_2_4_pre_009_all_52_current_typed_wrappers_and_legacy_forms_are_available_from_crate_root() {
let _get_account_info = ksp_onchain_transport_lib::HttpTransportPool::get_account_info;
let _get_balance = ksp_onchain_transport_lib::HttpTransportPool::get_balance;
let _get_largest_accounts = ksp_onchain_transport_lib::HttpTransportPool::get_largest_accounts;
let _get_minimum_balance_for_rent_exemption = ksp_onchain_transport_lib::HttpTransportPool::get_minimum_balance_for_rent_exemption;
let _get_multiple_accounts = ksp_onchain_transport_lib::HttpTransportPool::get_multiple_accounts;
let _get_program_accounts = ksp_onchain_transport_lib::HttpTransportPool::get_program_accounts;
let _get_token_account_balance = ksp_onchain_transport_lib::HttpTransportPool::get_token_account_balance;
let _get_token_accounts_by_delegate = ksp_onchain_transport_lib::HttpTransportPool::get_token_accounts_by_delegate;
let _get_token_accounts_by_owner = ksp_onchain_transport_lib::HttpTransportPool::get_token_accounts_by_owner;
let _get_token_largest_accounts = ksp_onchain_transport_lib::HttpTransportPool::get_token_largest_accounts;
let _get_token_supply = ksp_onchain_transport_lib::HttpTransportPool::get_token_supply;
let _get_fee_for_message = ksp_onchain_transport_lib::HttpTransportPool::get_fee_for_message;
let _get_latest_blockhash = ksp_onchain_transport_lib::HttpTransportPool::get_latest_blockhash;
let _get_recent_prioritization_fees = ksp_onchain_transport_lib::HttpTransportPool::get_recent_prioritization_fees;
let _get_signatures_for_address = ksp_onchain_transport_lib::HttpTransportPool::get_signatures_for_address;
let _get_signature_statuses = ksp_onchain_transport_lib::HttpTransportPool::get_signature_statuses;
let _get_transaction = ksp_onchain_transport_lib::HttpTransportPool::get_transaction;
let _get_transaction_count = ksp_onchain_transport_lib::HttpTransportPool::get_transaction_count;
let _is_blockhash_valid = ksp_onchain_transport_lib::HttpTransportPool::is_blockhash_valid;
let _request_airdrop = ksp_onchain_transport_lib::HttpTransportPool::request_airdrop;
let _send_transaction = ksp_onchain_transport_lib::HttpTransportPool::send_transaction;
let _simulate_transaction = ksp_onchain_transport_lib::HttpTransportPool::simulate_transaction;
let _get_block = ksp_onchain_transport_lib::HttpTransportPool::get_block;
let _get_block_commitment = ksp_onchain_transport_lib::HttpTransportPool::get_block_commitment;
let _get_block_height = ksp_onchain_transport_lib::HttpTransportPool::get_block_height;
let _get_block_production = ksp_onchain_transport_lib::HttpTransportPool::get_block_production;
let _get_blocks = ksp_onchain_transport_lib::HttpTransportPool::get_blocks;
let _get_blocks_with_limit = ksp_onchain_transport_lib::HttpTransportPool::get_blocks_with_limit;
let _get_block_time = ksp_onchain_transport_lib::HttpTransportPool::get_block_time;
let _get_first_available_block = ksp_onchain_transport_lib::HttpTransportPool::get_first_available_block;
let _get_recent_performance_samples = ksp_onchain_transport_lib::HttpTransportPool::get_recent_performance_samples;
let _minimum_ledger_slot = ksp_onchain_transport_lib::HttpTransportPool::minimum_ledger_slot;
let _get_cluster_nodes = ksp_onchain_transport_lib::HttpTransportPool::get_cluster_nodes;
let _get_epoch_info = ksp_onchain_transport_lib::HttpTransportPool::get_epoch_info;
let _get_epoch_schedule = ksp_onchain_transport_lib::HttpTransportPool::get_epoch_schedule;
let _get_genesis_hash = ksp_onchain_transport_lib::HttpTransportPool::get_genesis_hash;
let _get_health = ksp_onchain_transport_lib::HttpTransportPool::get_health;
let _get_highest_snapshot_slot = ksp_onchain_transport_lib::HttpTransportPool::get_highest_snapshot_slot;
let _get_identity = ksp_onchain_transport_lib::HttpTransportPool::get_identity;
let _get_leader_schedule = ksp_onchain_transport_lib::HttpTransportPool::get_leader_schedule;
let _get_max_retransmit_slot = ksp_onchain_transport_lib::HttpTransportPool::get_max_retransmit_slot;
let _get_max_shred_insert_slot = ksp_onchain_transport_lib::HttpTransportPool::get_max_shred_insert_slot;
let _get_slot = ksp_onchain_transport_lib::HttpTransportPool::get_slot;
let _get_slot_leader = ksp_onchain_transport_lib::HttpTransportPool::get_slot_leader;
let _get_slot_leaders = ksp_onchain_transport_lib::HttpTransportPool::get_slot_leaders;
let _get_version = ksp_onchain_transport_lib::HttpTransportPool::get_version;
let _get_vote_accounts = ksp_onchain_transport_lib::HttpTransportPool::get_vote_accounts;
let _get_inflation_governor = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_governor;
let _get_inflation_rate = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_rate;
let _get_inflation_reward = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_reward;
let _get_stake_minimum_delegation = ksp_onchain_transport_lib::HttpTransportPool::get_stake_minimum_delegation;
let _get_supply = ksp_onchain_transport_lib::HttpTransportPool::get_supply;
let _get_block_legacy = ksp_onchain_transport_lib::HttpTransportPool::get_block_legacy;
let _get_transaction_legacy = ksp_onchain_transport_lib::HttpTransportPool::get_transaction_legacy;
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs // file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 13 // version: 21
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract. //! Release-level completeness canaries for the staged HTTP wrapper sequence.
#[test] #[test]
fn release_registry_partition_matches_the_audited_http_plan() { fn release_registry_partition_matches_the_audited_http_plan() {
@@ -430,3 +430,306 @@ fn release_pre_008_ksp_transport_007_retro_audit_covers_all_typed_current_method
let get_transaction = ksp_onchain_transport_lib::find_http_rpc_method("getTransaction").expect("getTransaction descriptor must exist"); let get_transaction = ksp_onchain_transport_lib::find_http_rpc_method("getTransaction").expect("getTransaction descriptor must exist");
assert!(get_transaction.request_form_status().has_deprecated_legacy()); assert!(get_transaction.request_form_status().has_deprecated_legacy());
} }
#[test]
fn release_v0_2_4_descriptor_set_is_exact_and_remains_read_retry_safe_through_wrapper_completion() {
let mut expected_blocks = std::vec![
"getBlock",
"getBlockCommitment",
"getBlockHeight",
"getBlockProduction",
"getBlocks",
"getBlocksWithLimit",
"getBlockTime",
"getFirstAvailableBlock",
"getRecentPerformanceSamples",
"minimumLedgerSlot",
];
let mut expected_economics = std::vec!["getInflationGovernor", "getInflationRate", "getInflationReward", "getStakeMinimumDelegation", "getSupply",];
let mut actual_blocks = std::vec::Vec::new();
let mut actual_economics = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4 {
continue;
}
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
match descriptor.category() {
ksp_onchain_transport_lib::HttpRpcCategory::Blocks => actual_blocks.push(descriptor.method()),
ksp_onchain_transport_lib::HttpRpcCategory::Economics => actual_economics.push(descriptor.method()),
_ => {},
}
}
actual_blocks.sort_unstable();
actual_economics.sort_unstable();
expected_blocks.sort_unstable();
expected_economics.sort_unstable();
assert_eq!(actual_blocks, expected_blocks);
assert_eq!(actual_economics, expected_economics);
assert_eq!(actual_blocks.len(), 10);
assert_eq!(actual_economics.len(), 5);
let get_block = ksp_onchain_transport_lib::find_http_rpc_method("getBlock").expect("getBlock descriptor must exist");
assert!(get_block.request_form_status().has_deprecated_legacy());
}
#[test]
fn release_v0_2_4_pre_003_simple_blocks_subset_is_exact_and_retry_safe() {
let mut expected = std::vec!["getBlockCommitment", "getBlockHeight", "getBlockTime", "getFirstAvailableBlock", "minimumLedgerSlot"];
let mut actual = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
|| descriptor.category() != ksp_onchain_transport_lib::HttpRpcCategory::Blocks
{
continue;
}
match descriptor.method() {
"getBlockCommitment" | "getBlockHeight" | "getBlockTime" | "getFirstAvailableBlock" | "minimumLedgerSlot" => {
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
actual.push(descriptor.method());
},
_ => {},
}
}
actual.sort_unstable();
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(actual.len(), 5);
}
#[test]
fn release_v0_2_4_pre_004_range_performance_subset_is_exact_and_retry_safe() {
let mut expected = std::vec!["getBlocks", "getBlocksWithLimit", "getRecentPerformanceSamples"];
let mut actual = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
|| descriptor.category() != ksp_onchain_transport_lib::HttpRpcCategory::Blocks
{
continue;
}
match descriptor.method() {
"getBlocks" | "getBlocksWithLimit" | "getRecentPerformanceSamples" => {
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
actual.push(descriptor.method());
},
_ => {},
}
}
actual.sort_unstable();
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(actual.len(), 3);
}
#[test]
fn release_v0_2_4_pre_005_block_production_subset_is_exact_and_retry_safe() {
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method("getBlockProduction").expect("getBlockProduction descriptor must exist");
assert_eq!(descriptor.category(), ksp_onchain_transport_lib::HttpRpcCategory::Blocks);
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4);
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
}
#[test]
fn release_v0_2_4_pre_006_get_block_completes_all_ten_block_wrappers_with_legacy_marker() {
let expected = std::vec![
"getBlock",
"getBlockCommitment",
"getBlockHeight",
"getBlockProduction",
"getBlocks",
"getBlocksWithLimit",
"getBlockTime",
"getFirstAvailableBlock",
"getRecentPerformanceSamples",
"minimumLedgerSlot",
];
let mut actual = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() == ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
&& descriptor.category() == ksp_onchain_transport_lib::HttpRpcCategory::Blocks
{
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
actual.push(descriptor.method());
}
}
actual.sort_unstable();
let mut expected = expected;
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(actual.len(), 10);
let get_block = ksp_onchain_transport_lib::find_http_rpc_method("getBlock").expect("getBlock descriptor must exist");
assert!(get_block.request_form_status().has_deprecated_legacy());
}
#[test]
fn release_v0_2_4_pre_007_simple_economics_subset_is_exact_and_retry_safe() {
let mut expected = std::vec!["getInflationGovernor", "getInflationRate", "getStakeMinimumDelegation", "getSupply"];
let mut actual = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
|| descriptor.category() != ksp_onchain_transport_lib::HttpRpcCategory::Economics
{
continue;
}
assert!(matches!(
descriptor.method(),
"getInflationGovernor" | "getInflationRate" | "getInflationReward" | "getStakeMinimumDelegation" | "getSupply"
));
if descriptor.method() != "getInflationReward" {
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
actual.push(descriptor.method());
}
}
actual.sort_unstable();
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(actual.len(), 4);
}
#[test]
fn release_v0_2_4_pre_008_completes_all_five_economics_wrappers_exactly_and_retry_safe() {
let mut expected = std::vec!["getInflationGovernor", "getInflationRate", "getInflationReward", "getStakeMinimumDelegation", "getSupply"];
let mut actual = std::vec::Vec::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() == ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
&& descriptor.category() == ksp_onchain_transport_lib::HttpRpcCategory::Economics
{
actual.push(descriptor.method());
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
}
}
actual.sort_unstable();
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(actual.len(), 5);
}
#[test]
fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact() {
let expected_current = std::vec![
"getAccountInfo",
"getBalance",
"getBlock",
"getBlockCommitment",
"getBlockHeight",
"getBlockProduction",
"getBlocks",
"getBlocksWithLimit",
"getBlockTime",
"getClusterNodes",
"getEpochInfo",
"getEpochSchedule",
"getFeeForMessage",
"getFirstAvailableBlock",
"getGenesisHash",
"getHealth",
"getHighestSnapshotSlot",
"getIdentity",
"getInflationGovernor",
"getInflationRate",
"getInflationReward",
"getLargestAccounts",
"getLatestBlockhash",
"getLeaderSchedule",
"getMaxRetransmitSlot",
"getMaxShredInsertSlot",
"getMinimumBalanceForRentExemption",
"getMultipleAccounts",
"getProgramAccounts",
"getRecentPerformanceSamples",
"getRecentPrioritizationFees",
"getSignaturesForAddress",
"getSignatureStatuses",
"getSlot",
"getSlotLeader",
"getSlotLeaders",
"getStakeMinimumDelegation",
"getSupply",
"getTokenAccountBalance",
"getTokenAccountsByDelegate",
"getTokenAccountsByOwner",
"getTokenLargestAccounts",
"getTokenSupply",
"getTransaction",
"getTransactionCount",
"getVersion",
"getVoteAccounts",
"isBlockhashValid",
"minimumLedgerSlot",
"requestAirdrop",
"sendTransaction",
"simulateTransaction",
];
let expected_historical = std::vec![
"confirmTransaction",
"getConfirmedBlock",
"getConfirmedBlocks",
"getConfirmedBlocksWithLimit",
"getConfirmedSignaturesForAddress2",
"getConfirmedTransaction",
"getFeeCalculatorForBlockhash",
"getFeeRateGovernor",
"getFees",
"getRecentBlockhash",
"getSignatureConfirmation",
"getSignatureStatus",
"getSnapshotSlot",
"getStakeActivation",
];
let current = ksp_onchain_transport_lib::current_http_rpc_methods();
let historical = ksp_onchain_transport_lib::historical_http_rpc_methods();
let mut actual_current = std::vec::Vec::with_capacity(current.len());
let mut actual_historical = std::vec::Vec::with_capacity(historical.len());
let mut v0_2_1 = 0_usize;
let mut v0_2_2 = 0_usize;
let mut v0_2_3 = 0_usize;
let mut v0_2_4 = 0_usize;
let mut historical_current = 0_usize;
for descriptor in current {
actual_current.push(descriptor.method());
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Supported);
assert_ne!(descriptor.documentation_status(), ksp_onchain_transport_lib::RpcDocumentationStatus::Deprecated);
match descriptor.coverage_release() {
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_1 => v0_2_1 += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_2 => v0_2_2 += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3 => v0_2_3 += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4 => v0_2_4 += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical => historical_current += 1,
}
match descriptor.operation_kind() {
ksp_onchain_transport_lib::RpcOperationKind::Read | ksp_onchain_transport_lib::RpcOperationKind::Simulation => {
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
},
ksp_onchain_transport_lib::RpcOperationKind::WriteSubmission => {
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NeverAfterDispatch);
},
}
}
for descriptor in historical {
actual_historical.push(descriptor.method());
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);
}
actual_current.sort_unstable();
actual_historical.sort_unstable();
let mut expected_current = expected_current;
let mut expected_historical = expected_historical;
expected_current.sort_unstable();
expected_historical.sort_unstable();
assert_eq!(actual_current, expected_current);
assert_eq!(actual_historical, expected_historical);
assert_eq!(actual_current.len(), 52);
assert_eq!(actual_historical.len(), 14);
assert_eq!((v0_2_1, v0_2_2, v0_2_3, v0_2_4), (4, 22, 11, 15));
assert_eq!(historical_current, 0);
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs // file: crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs
// version: 4 // version: 5
//! Opt-in live Devnet smoke for representative pure Transport Accounts/Tokens/Cluster/Transactions reads. //! Opt-in live Devnet smoke for representative pure Transport reads across the complete HTTP typed surface.
fn devnet_pool() -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpTransportPool> { fn devnet_pool() -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpTransportPool> {
let role = ksp_onchain_transport_lib::HttpRoleName::new("default"); let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
@@ -42,7 +42,7 @@ fn devnet_pool() -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpTranspor
#[tokio::test(flavor = "current_thread")] #[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live Solana Devnet smoke; performs external network requests"] #[ignore = "opt-in live Solana Devnet smoke; performs external network requests"]
async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_and_transaction_read_wrappers() { async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transactions_blocks_and_economics_reads() {
let pool = devnet_pool().expect("programmatic Devnet Transport settings must construct a pool"); let pool = devnet_pool().expect("programmatic Devnet Transport settings must construct a pool");
let role = ksp_onchain_transport_lib::HttpRoleName::new("default"); let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
@@ -117,4 +117,19 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_and_trans
.await .await
.expect("Devnet getTransactionCount smoke must succeed"); .expect("Devnet getTransactionCount smoke must succeed");
assert!(transaction_count > 0); assert!(transaction_count > 0);
let block_height = pool.get_block_height(&role, std::option::Option::Some(&transaction_context)).await.expect("Devnet getBlockHeight smoke must succeed");
assert!(block_height > 0);
let inflation_rate = pool.get_inflation_rate(&role).await.expect("Devnet getInflationRate smoke must succeed");
assert!(inflation_rate.total().is_finite());
assert!(inflation_rate.validator().is_finite());
assert!(inflation_rate.foundation().is_finite());
let stake_minimum_delegation = pool
.get_stake_minimum_delegation(&role, std::option::Option::Some(&transaction_context))
.await
.expect("Devnet getStakeMinimumDelegation smoke must succeed");
assert!(stake_minimum_delegation.context().slot() > 0);
assert!(*stake_minimum_delegation.value() > 0);
} }

View File

@@ -0,0 +1,793 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
// version: 5
#[test]
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
assert_eq!(crate::SolanaTransactionDetails::Full.as_str(), "full");
assert_eq!(crate::SolanaTransactionDetails::Signatures.as_str(), "signatures");
assert_eq!(crate::SolanaTransactionDetails::None.as_str(), "none");
assert_eq!(crate::SolanaTransactionDetails::Accounts.as_str(), "accounts");
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(crate::SolanaTransactionDetails::Accounts),
std::option::Option::Some(1),
std::option::Option::Some(false),
);
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed));
assert_eq!(config.encoding(), std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed));
assert_eq!(config.transaction_details(), std::option::Option::Some(crate::SolanaTransactionDetails::Accounts));
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(1));
assert_eq!(config.rewards(), std::option::Option::Some(false));
assert_eq!(
config.to_json_value(),
serde_json::json!({
"commitment":"confirmed",
"encoding":"jsonParsed",
"transactionDetails":"accounts",
"maxSupportedTransactionVersion":1,
"rewards":false
})
);
assert!(crate::SolanaGetBlockConfig::default().is_empty());
assert_eq!(crate::SolanaGetBlockConfig::default().to_json_value(), serde_json::json!({}));
}
#[test]
fn block_production_config_preserves_identity_range_and_commitment() {
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::Some(430_000_099));
let config = crate::SolanaBlockProductionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(identity),
std::option::Option::Some(range),
);
assert_eq!(config.identity(), std::option::Option::Some(&identity));
assert_eq!(config.range().expect("range must be present").first_slot(), 430_000_000);
assert_eq!(config.range().expect("range must be present").last_slot(), std::option::Option::Some(430_000_099));
assert_eq!(
config.to_json_value(),
serde_json::json!({
"commitment":"finalized",
"identity":"11111111111111111111111111111111",
"range":{"firstSlot":430000000,"lastSlot":430000099}
})
);
assert!(crate::SolanaBlockProductionConfig::default().is_empty());
}
#[test]
fn block_commitment_fixture_preserves_nullable_distribution() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/block_commitment.variants.json"))
.expect("block commitment fixtures must decode");
let present = crate::SolanaBlockCommitment::decode_wire("getBlockCommitment", values[0].clone()).expect("present commitment must decode");
let absent = crate::SolanaBlockCommitment::decode_wire("getBlockCommitment", values[1].clone()).expect("null commitment must decode");
assert_eq!(present.commitment(), std::option::Option::Some(&[1, 2, 3, 4][..]));
assert_eq!(present.total_stake(), 1_000_000_000);
assert_eq!(absent.commitment(), std::option::Option::None);
}
#[test]
fn block_production_fixture_types_identity_keys_and_preserves_counts() {
let value =
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/block_production.v4_2_1.json")).expect("block production fixture must decode");
let result = crate::SolanaBlockProduction::decode_wire("getBlockProduction", value).expect("block production result must decode");
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
assert_eq!(result.by_identity().get(&identity), std::option::Option::Some(&(8, 7)));
assert_eq!(result.range().first_slot(), 430_000_000);
assert_eq!(result.range().last_slot(), 430_000_099);
}
#[test]
fn confirmed_block_fixture_preserves_rich_wire_and_simd_extensions() {
let value =
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/confirmed_block.v4_2_1.json")).expect("confirmed block fixture must decode");
let block = crate::SolanaConfirmedBlock::decode_wire("getBlock", value).expect("confirmed block must decode");
assert_eq!(block.parent_slot(), 430_000_122);
assert_eq!(block.block_time(), std::option::Option::Some(1_787_072_400));
assert_eq!(block.block_height(), std::option::Option::Some(410_000_000));
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
let transactions = block.transactions().value().expect("transactions must be present");
assert_eq!(transactions.len(), 2);
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
assert!(transactions[1].version().is_omitted());
assert!(transactions[1].meta().is_null());
let rewards = block.rewards().value().expect("rewards must be present");
assert_eq!(rewards.len(), 2);
assert_eq!(rewards[0].reward_type(), std::option::Option::Some("Fee"));
assert_eq!(rewards[0].commission(), std::option::Option::None);
assert_eq!(rewards[0].commission_bps().value(), std::option::Option::Some(&1_234));
assert_eq!(rewards[1].commission(), std::option::Option::Some(5));
assert!(rewards[1].commission_bps().is_omitted());
}
#[test]
fn confirmed_block_fixture_distinguishes_omitted_and_explicit_null_fields() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/confirmed_block.omissions.json"))
.expect("confirmed block omission fixtures must decode");
let omitted = crate::SolanaConfirmedBlock::decode_wire("getBlock", values[0].clone()).expect("omitted-fields block must decode");
let nulls = crate::SolanaConfirmedBlock::decode_wire("getBlock", values[1].clone()).expect("null-fields block must decode");
assert!(omitted.transactions().is_omitted());
assert!(omitted.signatures().is_omitted());
assert!(omitted.rewards().is_omitted());
assert!(omitted.num_reward_partitions().is_omitted());
assert!(nulls.transactions().is_null());
assert!(nulls.signatures().is_null());
assert!(nulls.rewards().is_null());
assert!(nulls.num_reward_partitions().is_null());
}
#[test]
fn performance_sample_fixture_accepts_current_and_older_shapes() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/performance_sample.variants.json"))
.expect("performance sample fixtures must decode");
let current = crate::SolanaPerformanceSample::decode_wire("getRecentPerformanceSamples", values[0].clone()).expect("current sample must decode");
let older = crate::SolanaPerformanceSample::decode_wire("getRecentPerformanceSamples", values[1].clone()).expect("older sample must decode");
assert_eq!(current.slot(), 430_000_123);
assert_eq!(current.num_transactions(), 250_000);
assert_eq!(current.num_non_vote_transactions(), std::option::Option::Some(175_000));
assert_eq!(current.num_slots(), 120);
assert_eq!(current.sample_period_secs(), 60);
assert_eq!(older.num_non_vote_transactions(), std::option::Option::None);
}
#[test]
fn block_reward_rejects_invalid_pubkey_without_echoing_value() {
let result = crate::SolanaConfirmedBlock::decode_wire(
"getBlock",
serde_json::json!({
"previousBlockhash":"previous",
"blockhash":"block",
"parentSlot":1,
"rewards":[{"pubkey":"not-a-pubkey","lamports":1,"postBalance":2,"rewardType":"Fee","commission":null}],
"blockTime":null,
"blockHeight":null
}),
);
let error = result.expect_err("invalid reward pubkey must fail closed");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
assert!(!error.to_string().contains("not-a-pubkey"));
}
fn pool_for_url(url: &str) -> crate::HttpTransportPool {
let role = crate::HttpEndpointRoleSettings::new(
crate::HttpRoleName::new("default"),
true,
std::vec![crate::HttpRequestKind::wildcard()],
10,
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
);
let endpoint = crate::HttpEndpointSettings::new(
"fixture",
true,
crate::HttpProviderName::new("fixture"),
crate::HttpClusterName::new("local"),
crate::HttpEndpointUrl::parse(url).expect("fixture URL must parse"),
std::time::Duration::from_secs(1),
std::time::Duration::from_secs(1),
std::option::Option::Some(1),
std::vec![role],
);
let settings = crate::HttpTransportSettings::new(
std::vec![endpoint],
crate::HttpRetrySettings::new(0, std::time::Duration::from_millis(1), std::time::Duration::from_millis(1)),
);
return crate::HttpTransportPool::new(settings).expect("fixture pool must build");
}
fn serve_once(body: &'static str) -> (std::string::String, std::thread::JoinHandle<std::string::String>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind");
let address = listener.local_addr().expect("fixture listener address must resolve");
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("fixture server must accept one request");
let request = read_request(&mut stream);
let response = format!("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", body.len(), body);
std::io::Write::write_all(&mut stream, response.as_bytes()).expect("fixture response must write");
return request;
});
return (format!("http://{address}"), handle);
}
fn read_request(stream: &mut std::net::TcpStream) -> std::string::String {
let mut bytes = std::vec::Vec::new();
let mut buffer = [0_u8; 1024];
loop {
let count = std::io::Read::read(stream, &mut buffer).expect("fixture request must read");
if count == 0 {
break;
}
bytes.extend_from_slice(&buffer[..count]);
if request_complete(bytes.as_slice()) {
break;
}
}
return std::string::String::from_utf8(bytes).expect("fixture request must be UTF-8");
}
fn request_complete(bytes: &[u8]) -> bool {
let text = match std::str::from_utf8(bytes) {
std::result::Result::Ok(text) => text,
std::result::Result::Err(_) => return false,
};
let header_end = match text.find("\r\n\r\n") {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let mut content_length = 0_usize;
for line in text[..header_end].lines() {
let (name, value) = match line.split_once(':') {
std::option::Option::Some(parts) => parts,
std::option::Option::None => continue,
};
if name.eq_ignore_ascii_case("content-length") {
content_length = value.trim().parse::<usize>().expect("content length must parse");
}
}
return bytes.len() >= header_end.saturating_add(4).saturating_add(content_length);
}
fn request_body(request: &str) -> serde_json::Value {
let body = request.split("\r\n\r\n").nth(1).expect("fixture request body must exist");
return serde_json::from_str(body).expect("fixture request body must be JSON");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_commitment_serializes_slot_and_preserves_distribution() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_commitment.success.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block_commitment(&crate::HttpRoleName::new("default"), 430_000_123).await.expect("block commitment fixture must succeed");
assert_eq!(result.commitment(), std::option::Option::Some(&[1, 2, 3, 4][..]));
assert_eq!(result.total_stake(), 1_000_000_000);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getBlockCommitment"));
assert_eq!(body["params"], serde_json::json!([430000123]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_height_serializes_context_config() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_height.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(429_000_000));
let height = pool
.get_block_height(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("block height fixture must succeed");
assert_eq!(height, 410_000_000);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"finalized","minContextSlot":429000000}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_height_omits_explicitly_empty_config() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_height.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaContextConfig::default();
let height = pool
.get_block_height(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("block height fixture must succeed");
assert_eq!(height, 410_000_000);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_time_preserves_timestamp_and_null() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_time.success.json"));
let pool = pool_for_url(url.as_str());
let timestamp = pool.get_block_time(&crate::HttpRoleName::new("default"), 430_000_123).await.expect("block time fixture must succeed");
assert_eq!(timestamp, std::option::Option::Some(1_787_072_400));
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000123]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_time.null.json"));
let pool = pool_for_url(url.as_str());
let timestamp = pool.get_block_time(&crate::HttpRoleName::new("default"), 430_000_124).await.expect("null block time fixture must succeed");
assert_eq!(timestamp, std::option::Option::None);
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_first_available_block_has_no_params() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_first_available_block.success.json"));
let pool = pool_for_url(url.as_str());
let slot = pool.get_first_available_block(&crate::HttpRoleName::new("default")).await.expect("first available block fixture must succeed");
assert_eq!(slot, 250_000);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getFirstAvailableBlock"));
assert_eq!(body["params"], serde_json::json!([]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_minimum_ledger_slot_has_no_params() {
let (url, handle) = serve_once(include_str!("../fixtures/http/minimum_ledger_slot.success.json"));
let pool = pool_for_url(url.as_str());
let slot = pool.minimum_ledger_slot(&crate::HttpRoleName::new("default")).await.expect("minimum ledger slot fixture must succeed");
assert_eq!(slot, 123_456);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("minimumLedgerSlot"));
assert_eq!(body["params"], serde_json::json!([]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_blocks_covers_all_four_overloads_and_preserves_server_order() {
let role = crate::HttpRoleName::new("default");
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(429_999_999));
let empty_config = crate::SolanaContextConfig::default();
let cases = [
(std::option::Option::None, std::option::Option::None, serde_json::json!([430000100])),
(std::option::Option::Some(430_000_109), std::option::Option::None, serde_json::json!([430000100, 430000109])),
(
std::option::Option::None,
std::option::Option::Some(&config),
serde_json::json!([430000100,{"commitment":"confirmed","minContextSlot":429999999}]),
),
(
std::option::Option::Some(430_000_109),
std::option::Option::Some(&config),
serde_json::json!([430000100,430000109,{"commitment":"confirmed","minContextSlot":429999999}]),
),
(std::option::Option::None, std::option::Option::Some(&empty_config), serde_json::json!([430000100, {}])),
];
for (end_slot, config, expected_params) in cases {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool.get_blocks(&role, 430_000_100, end_slot, config).await.expect("getBlocks overload fixture must succeed");
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getBlocks"));
assert_eq!(body["params"], expected_params);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversized_before_io() {
let role = crate::HttpRoleName::new("default");
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.empty.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool
.get_blocks(&role, 430_000_100, std::option::Option::Some(430_000_099), std::option::Option::None)
.await
.expect("reversed getBlocks range must remain a valid RPC request");
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000100, 430000099]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool
.get_blocks(&role, 10, std::option::Option::Some(500_010), std::option::Option::None)
.await
.expect("500000-slot getBlocks range must remain valid");
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([10, 500010]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks(&role, 10, std::option::Option::Some(500_011), std::option::Option::None).await;
let error = result.expect_err("range above 500000 must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "start_slot");
assert_eq!(error.context()[1].value(), "10");
assert_eq!(error.context()[2].key(), "end_slot");
assert_eq!(error.context()[2].value(), "500011");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_block_range_wrappers_reject_processed_commitment_before_io() {
let role = crate::HttpRoleName::new("default");
let pool = pool_for_url("http://127.0.0.1:9");
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Processed), std::option::Option::None);
let get_blocks = pool.get_blocks(&role, 1, std::option::Option::None, std::option::Option::Some(&config)).await;
let error = get_blocks.expect_err("getBlocks processed commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
let get_blocks_with_limit = pool.get_blocks_with_limit(&role, 1, 1, std::option::Option::Some(&config)).await;
let error = get_blocks_with_limit.expect_err("getBlocksWithLimit processed commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_maximum() {
let role = crate::HttpRoleName::new("default");
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.empty.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool.get_blocks_with_limit(&role, 430_000_200, 0, std::option::Option::None).await.expect("zero getBlocksWithLimit limit must remain valid");
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200, 0]));
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(430_000_000));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.success.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool
.get_blocks_with_limit(&role, 430_000_200, 500_000, std::option::Option::Some(&config))
.await
.expect("maximum getBlocksWithLimit limit must remain valid");
assert_eq!(blocks, std::vec![430_000_200, 430_000_201, 430_000_205]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200,500000,{"commitment":"finalized","minContextSlot":430000000}]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks_with_limit(&role, 1, 500_001, std::option::Option::None).await;
let error = result.expect_err("getBlocksWithLimit above 500000 must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "limit");
assert_eq!(error.context()[1].value(), "500001");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_recent_performance_samples_preserves_default_explicit_limit_order_and_older_shape() {
let role = crate::HttpRoleName::new("default");
for (limit, expected_params) in [(std::option::Option::None, serde_json::json!([])), (std::option::Option::Some(720), serde_json::json!([720]))] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_recent_performance_samples.success.json"));
let pool = pool_for_url(url.as_str());
let samples = pool.get_recent_performance_samples(&role, limit).await.expect("performance sample fixture must succeed");
assert_eq!(samples.len(), 2);
assert_eq!(samples[0].slot(), 430_000_123);
assert_eq!(samples[0].num_transactions(), 250_000);
assert_eq!(samples[0].num_non_vote_transactions(), std::option::Option::Some(175_000));
assert_eq!(samples[1].slot(), 430_000_000);
assert_eq!(samples[1].num_non_vote_transactions(), std::option::Option::None);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getRecentPerformanceSamples"));
assert_eq!(body["params"], expected_params);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_recent_performance_samples_rejects_above_720_before_io() {
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_recent_performance_samples(&crate::HttpRoleName::new("default"), std::option::Option::Some(721)).await;
let error = result.expect_err("performance sample limit above 720 must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "limit");
assert_eq!(error.context()[1].value(), "721");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_production_serializes_full_config_and_decodes_contextual_result() {
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::Some(430_000_099));
let config = crate::SolanaBlockProductionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(identity),
std::option::Option::Some(range),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
let pool = pool_for_url(url.as_str());
let response = pool
.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("block production fixture must succeed");
assert_eq!(response.context().slot(), 430_000_123);
assert_eq!(response.context().api_version(), std::option::Option::Some("4.2.1"));
assert_eq!(response.value().by_identity().get(&identity), std::option::Option::Some(&(8, 7)));
assert_eq!(response.value().range().first_slot(), 430_000_000);
assert_eq!(response.value().range().last_slot(), 430_000_099);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getBlockProduction"));
assert_eq!(
body["params"],
serde_json::json!([{
"commitment":"finalized",
"identity":"11111111111111111111111111111111",
"range":{"firstSlot":430000000,"lastSlot":430000099}
}])
);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_production_omits_empty_config_and_supports_open_range_with_processed_commitment() {
let role = crate::HttpRoleName::new("default");
let empty = crate::SolanaBlockProductionConfig::default();
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
let pool = pool_for_url(url.as_str());
let response = pool.get_block_production(&role, std::option::Option::Some(&empty)).await.expect("empty block production config must succeed");
assert_eq!(response.value().range().last_slot(), 430_000_099);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::None);
let config = crate::SolanaBlockProductionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Processed),
std::option::Option::None,
std::option::Option::Some(range),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
let pool = pool_for_url(url.as_str());
pool.get_block_production(&role, std::option::Option::Some(&config)).await.expect("open block production range must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"processed","range":{"firstSlot":430000000}}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_production_rejects_reversed_range_before_io() {
let range = crate::SolanaBlockProductionRange::new(430_000_100, std::option::Option::Some(430_000_099));
let config = crate::SolanaBlockProductionConfig::new(std::option::Option::None, std::option::Option::None, std::option::Option::Some(range));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config)).await;
let error = result.expect_err("reversed block production range must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "first_slot");
assert_eq!(error.context()[1].value(), "430000100");
assert_eq!(error.context()[2].key(), "last_slot");
assert_eq!(error.context()[2].value(), "430000099");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_production_rejects_invalid_wire_identity_without_echoing_value() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.invalid_identity.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::None).await;
let error = result.expect_err("invalid block production identity must fail closed");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
assert!(!error.to_string().contains("not-a-pubkey"));
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_modern_full_config_preserves_rich_wire_and_simd_fields() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.full.success.json"));
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config))
.await
.expect("modern getBlock fixture must succeed")
.expect("modern getBlock fixture must contain a block");
assert_eq!(block.parent_slot(), 430_000_122);
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
let transactions = block.transactions().value().expect("full block transactions must be present");
assert_eq!(transactions.len(), 2);
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
let first_meta = transactions[0].meta().value().expect("full transaction metadata must be present");
assert_eq!(first_meta["rewards"][0]["commissionBps"], serde_json::json!(1234));
let rewards = block.rewards().value().expect("block rewards must be present");
assert_eq!(rewards[0].commission(), std::option::Option::None);
assert_eq!(rewards[0].commission_bps().value(), std::option::Option::Some(&1_234));
assert!(rewards[1].commission_bps().is_omitted());
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getBlock"));
assert_eq!(
body["params"],
serde_json::json!([430000123,{"commitment":"finalized","encoding":"jsonParsed","transactionDetails":"full","maxSupportedTransactionVersion":1,"rewards":true}])
);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_absent_and_explicit_empty_modern_config() {
let role = crate::HttpRoleName::new("default");
for (config, expected_params) in [
(std::option::Option::None, serde_json::json!([430000123])),
(std::option::Option::Some(crate::SolanaGetBlockConfig::default()), serde_json::json!([430000123, {}])),
] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.null.json"));
let pool = pool_for_url(url.as_str());
let block = pool.get_block(&role, 430_000_123, config.as_ref()).await.expect("nullable getBlock request must succeed");
assert!(block.is_none());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], expected_params);
}
}
#[allow(deprecated)]
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_legacy_covers_all_bare_encoding_labels_and_response_shapes() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionEncoding::Binary, "binary", include_str!("../fixtures/http/get_block.binary.success.json")),
(crate::SolanaTransactionEncoding::Base58, "base58", include_str!("../fixtures/http/get_block.base58.success.json")),
(crate::SolanaTransactionEncoding::Base64, "base64", include_str!("../fixtures/http/get_block.base64.success.json")),
(crate::SolanaTransactionEncoding::Json, "json", include_str!("../fixtures/http/get_block.json.success.json")),
(crate::SolanaTransactionEncoding::JsonParsed, "jsonParsed", include_str!("../fixtures/http/get_block.json_parsed.success.json")),
];
for (encoding, wire_label, response) in cases {
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block_legacy(&role, 430_000_123, encoding)
.await
.expect("legacy getBlock encoding must succeed")
.expect("legacy getBlock encoding must return a block");
let transactions = block.transactions().value().expect("legacy full response must contain transactions");
assert_eq!(transactions.len(), 1);
match encoding {
crate::SolanaTransactionEncoding::Binary => assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::LegacyBinary(_))),
crate::SolanaTransactionEncoding::Base58 => assert!(matches!(
transactions[0].transaction(),
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base58, .. }
)),
crate::SolanaTransactionEncoding::Base64 => assert!(matches!(
transactions[0].transaction(),
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. }
)),
crate::SolanaTransactionEncoding::Json | crate::SolanaTransactionEncoding::JsonParsed => {
assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::Json(_)));
},
}
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000123, wire_label]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_modern_covers_all_agave_v4_2_1_encoding_labels() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionEncoding::Binary, "binary", include_str!("../fixtures/http/get_block.binary.success.json")),
(crate::SolanaTransactionEncoding::Base58, "base58", include_str!("../fixtures/http/get_block.base58.success.json")),
(crate::SolanaTransactionEncoding::Base64, "base64", include_str!("../fixtures/http/get_block.base64.success.json")),
(crate::SolanaTransactionEncoding::Json, "json", include_str!("../fixtures/http/get_block.json.success.json")),
(crate::SolanaTransactionEncoding::JsonParsed, "jsonParsed", include_str!("../fixtures/http/get_block.json_parsed.success.json")),
];
for (encoding, wire_label, response) in cases {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(encoding),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
pool.get_block(&role, 430_000_123, std::option::Option::Some(&config))
.await
.expect("modern getBlock encoding must succeed")
.expect("modern getBlock encoding must return a block");
let request = handle.join().expect("fixture server must join");
assert_eq!(
request_body(request.as_str())["params"],
serde_json::json!([430000123,{"commitment":"confirmed","encoding":wire_label,"transactionDetails":"full","maxSupportedTransactionVersion":1,"rewards":true}])
);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_covers_full_signatures_none_and_accounts_transaction_details() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionDetails::Full, "full", include_str!("../fixtures/http/get_block.full_no_rewards.success.json")),
(crate::SolanaTransactionDetails::Signatures, "signatures", include_str!("../fixtures/http/get_block.signatures.success.json")),
(crate::SolanaTransactionDetails::None, "none", include_str!("../fixtures/http/get_block.none.success.json")),
(crate::SolanaTransactionDetails::Accounts, "accounts", include_str!("../fixtures/http/get_block.accounts.success.json")),
];
for (details, wire_label, response) in cases {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(details),
std::option::Option::Some(1),
std::option::Option::Some(false),
);
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&role, 430_000_123, std::option::Option::Some(&config))
.await
.expect("transactionDetails getBlock fixture must succeed")
.expect("transactionDetails getBlock fixture must return a block");
match details {
crate::SolanaTransactionDetails::Full => {
let transactions = block.transactions().value().expect("full transaction list must be present");
assert_eq!(transactions.len(), 1);
assert!(block.signatures().is_omitted());
assert_eq!(transactions[0].meta().value().expect("full transaction metadata must be present")["rewards"], serde_json::Value::Null);
},
crate::SolanaTransactionDetails::Signatures => {
assert!(block.transactions().is_omitted());
assert_eq!(block.signatures().value().expect("signatures must be present"), &std::vec!["sig-a".to_owned(), "sig-b".to_owned()]);
},
crate::SolanaTransactionDetails::None => {
assert!(block.transactions().is_omitted());
assert!(block.signatures().is_omitted());
},
crate::SolanaTransactionDetails::Accounts => {
let transactions = block.transactions().value().expect("accounts transaction list must be present");
assert_eq!(transactions.len(), 1);
assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::Json(_)));
if let crate::SolanaEncodedTransaction::Json(transaction) = transactions[0].transaction() {
assert_eq!(transaction["accountKeys"][0]["pubkey"], serde_json::json!("11111111111111111111111111111111"));
}
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
},
}
assert!(block.rewards().is_omitted());
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
let request = handle.join().expect("fixture server must join");
assert_eq!(
request_body(request.as_str())["params"],
serde_json::json!([430000123,{"commitment":"confirmed","encoding":"jsonParsed","transactionDetails":wire_label,"maxSupportedTransactionVersion":1,"rewards":false}])
);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_null_and_omitted_top_level_wire_states() {
let role = crate::HttpRoleName::new("default");
for (response, expect_null) in [
(include_str!("../fixtures/http/get_block.omitted_fields.success.json"), false),
(include_str!("../fixtures/http/get_block.null_fields.success.json"), true),
] {
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&role, 430_000_123, std::option::Option::None)
.await
.expect("wire-state getBlock fixture must succeed")
.expect("wire-state getBlock fixture must return a block");
if expect_null {
assert!(block.transactions().is_null());
assert!(block.signatures().is_null());
assert!(block.rewards().is_null());
assert!(block.num_reward_partitions().is_null());
} else {
assert!(block.transactions().is_omitted());
assert!(block.signatures().is_omitted());
assert!(block.rewards().is_omitted());
assert!(block.num_reward_partitions().is_omitted());
}
handle.join().expect("fixture server must join");
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_unsupported_version_rpc_error() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(crate::SolanaTransactionEncoding::Base64),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(0),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.error_unsupported_version.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config)).await;
let error = result.expect_err("unsupported transaction version must remain an RPC application error");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_block_not_available_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.error_block_not_available.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::None).await;
let error = result.expect_err("unavailable block must remain an RPC application error");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_rejects_processed_commitment_before_io() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Processed),
std::option::Option::Some(crate::SolanaTransactionEncoding::Json),
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
);
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config)).await;
let error = result.expect_err("processed getBlock commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
}

View File

@@ -0,0 +1,492 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
// version: 3
#[test]
fn inflation_reward_config_preserves_epoch_commitment_and_min_context_slot() {
let config = crate::SolanaInflationRewardConfig::new(
std::option::Option::Some(811),
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(429_000_000),
);
assert_eq!(config.epoch(), std::option::Option::Some(811));
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
assert_eq!(config.min_context_slot(), std::option::Option::Some(429_000_000));
assert_eq!(config.to_json_value(), serde_json::json!({"epoch":811,"commitment":"finalized","minContextSlot":429000000}));
assert!(crate::SolanaInflationRewardConfig::default().is_empty());
}
#[test]
fn supply_config_distinguishes_omitted_from_explicit_false() {
let omitted = crate::SolanaSupplyConfig::default();
let explicit_false = crate::SolanaSupplyConfig::new(std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(false));
assert!(omitted.is_empty());
assert_eq!(omitted.to_json_value(), serde_json::json!({}));
assert_eq!(explicit_false.exclude_non_circulating_accounts_list(), std::option::Option::Some(false));
assert_eq!(explicit_false.to_json_value(), serde_json::json!({"commitment":"confirmed","excludeNonCirculatingAccountsList":false}));
}
#[test]
fn inflation_governor_and_rate_fixtures_preserve_runtime_values() {
let governor_value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/inflation_governor.v4_2_1.json"))
.expect("inflation governor fixture must decode");
let rate_value =
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/inflation_rate.v4_2_1.json")).expect("inflation rate fixture must decode");
let governor = crate::SolanaInflationGovernor::decode_wire("getInflationGovernor", governor_value).expect("inflation governor must decode");
let rate = crate::SolanaInflationRate::decode_wire("getInflationRate", rate_value).expect("inflation rate must decode");
assert_eq!(governor.initial(), 0.08);
assert_eq!(governor.terminal(), 0.015);
assert_eq!(governor.taper(), 0.15);
assert_eq!(governor.foundation(), 0.05);
assert_eq!(governor.foundation_term(), 7.0);
assert_eq!(rate.total(), 0.042);
assert_eq!(rate.validator(), 0.041);
assert_eq!(rate.foundation(), 0.001);
assert_eq!(rate.epoch(), 812);
}
#[test]
fn inflation_reward_fixture_preserves_position_null_and_commission_bps_states() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/inflation_reward.variants.json"))
.expect("inflation reward fixtures must decode");
let first = crate::SolanaInflationReward::decode_wire("getInflationReward", values[0].clone()).expect("first reward must decode");
let third = crate::SolanaInflationReward::decode_wire("getInflationReward", values[2].clone()).expect("third reward must decode");
assert_eq!(values.len(), 3);
assert!(values[1].is_null());
assert_eq!(first.epoch(), 811);
assert_eq!(first.effective_slot(), 429_900_000);
assert_eq!(first.amount(), 2_500_000);
assert_eq!(first.post_balance(), 1_002_500_000);
assert_eq!(first.commission(), std::option::Option::Some(7));
assert_eq!(first.commission_bps().value(), std::option::Option::Some(&725));
assert_eq!(third.commission(), std::option::Option::None);
assert!(third.commission_bps().is_omitted());
}
#[test]
fn supply_fixture_types_non_circulating_accounts_and_preserves_order() {
let value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/supply.v4_2_1.json")).expect("supply fixture must decode");
let supply = crate::SolanaSupply::decode_wire("getSupply", value).expect("supply must decode");
assert_eq!(supply.total(), 600_000_000_000_000_000);
assert_eq!(supply.circulating(), 500_000_000_000_000_000);
assert_eq!(supply.non_circulating(), 100_000_000_000_000_000);
assert_eq!(supply.non_circulating_accounts().len(), 2);
assert_eq!(supply.non_circulating_accounts()[0].to_string(), "11111111111111111111111111111111");
assert_eq!(supply.non_circulating_accounts()[1].to_string(), "Vote111111111111111111111111111111111111111");
}
#[test]
fn supply_rejects_invalid_non_circulating_pubkey_without_echoing_value() {
let result = crate::SolanaSupply::decode_wire(
"getSupply",
serde_json::json!({"total":3,"circulating":2,"nonCirculating":1,"nonCirculatingAccounts":["invalid-pubkey"]}),
);
let error = result.expect_err("invalid supply pubkey must fail closed");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
assert!(!error.to_string().contains("invalid-pubkey"));
}
fn pool_for_url(url: &str) -> crate::HttpTransportPool {
let role = crate::HttpEndpointRoleSettings::new(
crate::HttpRoleName::new("default"),
true,
std::vec![crate::HttpRequestKind::wildcard()],
10,
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
);
let endpoint = crate::HttpEndpointSettings::new(
"fixture",
true,
crate::HttpProviderName::new("fixture"),
crate::HttpClusterName::new("local"),
crate::HttpEndpointUrl::parse(url).expect("fixture URL must parse"),
std::time::Duration::from_secs(1),
std::time::Duration::from_secs(1),
std::option::Option::Some(1),
std::vec![role],
);
let settings = crate::HttpTransportSettings::new(
std::vec![endpoint],
crate::HttpRetrySettings::new(0, std::time::Duration::from_millis(1), std::time::Duration::from_millis(1)),
);
return crate::HttpTransportPool::new(settings).expect("fixture pool must build");
}
fn serve_once(body: &'static str) -> (std::string::String, std::thread::JoinHandle<std::string::String>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind");
let address = listener.local_addr().expect("fixture listener address must resolve");
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("fixture server must accept one request");
let request = read_request(&mut stream);
let response = format!("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", body.len(), body);
std::io::Write::write_all(&mut stream, response.as_bytes()).expect("fixture response must write");
return request;
});
return (format!("http://{address}"), handle);
}
fn read_request(stream: &mut std::net::TcpStream) -> std::string::String {
let mut bytes = std::vec::Vec::new();
let mut buffer = [0_u8; 1024];
loop {
let count = std::io::Read::read(stream, &mut buffer).expect("fixture request must read");
if count == 0 {
break;
}
bytes.extend_from_slice(&buffer[..count]);
if request_complete(bytes.as_slice()) {
break;
}
}
return std::string::String::from_utf8(bytes).expect("fixture request must be UTF-8");
}
fn request_complete(bytes: &[u8]) -> bool {
let text = match std::str::from_utf8(bytes) {
std::result::Result::Ok(text) => text,
std::result::Result::Err(_) => return false,
};
let header_end = match text.find("\r\n\r\n") {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let mut content_length = 0_usize;
for line in text[..header_end].lines() {
let (name, value) = match line.split_once(':') {
std::option::Option::Some(parts) => parts,
std::option::Option::None => continue,
};
if name.eq_ignore_ascii_case("content-length") {
content_length = value.trim().parse::<usize>().expect("content length must parse");
}
}
return bytes.len() >= header_end.saturating_add(4).saturating_add(content_length);
}
fn request_body(request: &str) -> serde_json::Value {
let body = request.split("\r\n\r\n").nth(1).expect("fixture request body must exist");
return serde_json::from_str(body).expect("fixture request body must be JSON");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_governor_serializes_processed_commitment_and_preserves_runtime_values() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_governor.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaCommitmentConfig::new(std::option::Option::Some(crate::SolanaCommitment::Processed));
let governor = pool
.get_inflation_governor(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("inflation governor fixture must succeed");
assert_eq!(governor.initial(), 0.07123456789);
assert_eq!(governor.terminal(), 0.0123456789);
assert_eq!(governor.taper(), 0.2718281828);
assert_eq!(governor.foundation(), 0.0042);
assert_eq!(governor.foundation_term(), 6.25);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getInflationGovernor"));
assert_eq!(body["params"], serde_json::json!([{"commitment":"processed"}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_governor_omits_absent_and_empty_config() {
let role = crate::HttpRoleName::new("default");
let empty = crate::SolanaCommitmentConfig::default();
for config in [std::option::Option::None, std::option::Option::Some(&empty)] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_governor.success.json"));
let pool = pool_for_url(url.as_str());
pool.get_inflation_governor(&role, config).await.expect("inflation governor fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_rate_has_no_params_and_preserves_runtime_values() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_rate.success.json"));
let pool = pool_for_url(url.as_str());
let rate = pool.get_inflation_rate(&crate::HttpRoleName::new("default")).await.expect("inflation rate fixture must succeed");
assert_eq!(rate.total(), 0.0387654321);
assert_eq!(rate.validator(), 0.037654321);
assert_eq!(rate.foundation(), 0.0011111111);
assert_eq!(rate.epoch(), 913);
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getInflationRate"));
assert_eq!(body["params"], serde_json::json!([]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_stake_minimum_delegation_preserves_context_and_runtime_value_without_local_minimum() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_stake_minimum_delegation.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Processed), std::option::Option::Some(431_000_000));
let result = pool
.get_stake_minimum_delegation(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("stake minimum delegation fixture must succeed");
assert_eq!(*result.value(), 42_424_242);
assert_eq!(result.context().slot(), 431_000_123);
assert_eq!(result.context().api_version(), std::option::Option::Some("4.2.1"));
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"processed","minContextSlot":431000000}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_stake_minimum_delegation_omits_absent_and_empty_config() {
let role = crate::HttpRoleName::new("default");
let empty = crate::SolanaContextConfig::default();
for config in [std::option::Option::None, std::option::Option::Some(&empty)] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_stake_minimum_delegation.success.json"));
let pool = pool_for_url(url.as_str());
pool.get_stake_minimum_delegation(&role, config).await.expect("stake minimum delegation fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_supply_preserves_explicit_false_full_account_list_and_context() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_supply.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaSupplyConfig::new(std::option::Option::Some(crate::SolanaCommitment::Processed), std::option::Option::Some(false));
let result = pool.get_supply(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config)).await.expect("supply fixture must succeed");
assert_eq!(result.context().slot(), 431_000_124);
assert_eq!(result.value().total(), 610_000_000_000_000_000);
assert_eq!(result.value().circulating(), 510_000_000_000_000_000);
assert_eq!(result.value().non_circulating(), 100_000_000_000_000_000);
assert_eq!(result.value().non_circulating_accounts().len(), 2);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"processed","excludeNonCirculatingAccountsList":false}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_supply_preserves_explicit_true_as_empty_runtime_account_list() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_supply.excluded.success.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaSupplyConfig::new(std::option::Option::None, std::option::Option::Some(true));
let result = pool
.get_supply(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
.await
.expect("excluded supply fixture must succeed");
assert_eq!(result.value().non_circulating_accounts(), &[]);
assert_eq!(result.value().non_circulating(), 100_000_000_000_000_000);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"excludeNonCirculatingAccountsList":true}]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_supply_omits_absent_and_empty_config() {
let role = crate::HttpRoleName::new("default");
let empty = crate::SolanaSupplyConfig::default();
for config in [std::option::Option::None, std::option::Option::Some(&empty)] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_supply.success.json"));
let pool = pool_for_url(url.as_str());
pool.get_supply(&role, config).await.expect("supply fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_stake_minimum_delegation_preserves_min_context_slot_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_stake_minimum_delegation.error_min_context.json"));
let pool = pool_for_url(url.as_str());
let config = crate::SolanaContextConfig::new(std::option::Option::None, std::option::Option::Some(431_000_000));
let result = pool.get_stake_minimum_delegation(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config)).await;
let error = result.expect_err("min-context-slot RPC error must propagate");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_supply_preserves_scan_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_supply.error_scan.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_supply(&crate::HttpRoleName::new("default"), std::option::Option::None).await;
let error = result.expect_err("supply scan RPC error must propagate");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}
fn inflation_reward_fixture_pubkey(value: &str) -> ksp_core_lib::Pubkey {
return value.parse::<ksp_core_lib::Pubkey>().expect("fixture inflation-reward pubkey must parse");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_serializes_full_config_and_preserves_position_and_commission_states() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.success.json"));
let pool = pool_for_url(url.as_str());
let addresses = std::vec![
inflation_reward_fixture_pubkey("11111111111111111111111111111111"),
inflation_reward_fixture_pubkey("Vote111111111111111111111111111111111111111"),
inflation_reward_fixture_pubkey("SysvarRent111111111111111111111111111111111"),
inflation_reward_fixture_pubkey("Stake11111111111111111111111111111111111111"),
];
let config = crate::SolanaInflationRewardConfig::new(
std::option::Option::Some(912),
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(431_000_000),
);
let rewards = pool
.get_inflation_reward(&crate::HttpRoleName::new("default"), addresses.as_slice(), std::option::Option::Some(&config))
.await
.expect("inflation reward fixture must succeed");
assert_eq!(rewards.len(), addresses.len());
let first = rewards[0].as_ref().expect("first reward must be present");
assert_eq!(first.epoch(), 912);
assert_eq!(first.effective_slot(), 431_500_001);
assert_eq!(first.amount(), 2_500_000);
assert_eq!(first.post_balance(), 1_002_500_000);
assert_eq!(first.commission(), std::option::Option::Some(7));
assert_eq!(first.commission_bps().value(), std::option::Option::Some(&725));
assert!(rewards[1].is_none());
let third = rewards[2].as_ref().expect("third reward must be present");
assert_eq!(third.commission(), std::option::Option::None);
assert!(third.commission_bps().is_omitted());
let fourth = rewards[3].as_ref().expect("fourth reward must be present");
assert_eq!(fourth.commission(), std::option::Option::Some(3));
assert!(fourth.commission_bps().is_null());
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getInflationReward"));
assert_eq!(
body["params"],
serde_json::json!([
[
"11111111111111111111111111111111",
"Vote111111111111111111111111111111111111111",
"SysvarRent111111111111111111111111111111111",
"Stake11111111111111111111111111111111111111"
],
{"epoch":912,"commitment":"finalized","minContextSlot":431000000}
])
);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_preserves_duplicate_input_order_and_positional_nulls() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.duplicates.json"));
let pool = pool_for_url(url.as_str());
let first = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let second = inflation_reward_fixture_pubkey("Vote111111111111111111111111111111111111111");
let addresses = std::vec![first, second, first];
let rewards = pool
.get_inflation_reward(&crate::HttpRoleName::new("default"), addresses.as_slice(), std::option::Option::None)
.await
.expect("duplicate inflation reward fixture must succeed");
assert_eq!(rewards.len(), 3);
assert_eq!(rewards[0], rewards[2]);
assert!(rewards[1].is_none());
let request = handle.join().expect("fixture server must join");
assert_eq!(
request_body(request.as_str())["params"],
serde_json::json!([["11111111111111111111111111111111", "Vote111111111111111111111111111111111111111", "11111111111111111111111111111111"]])
);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_omits_absent_and_empty_config_but_keeps_address_parameter() {
let role = crate::HttpRoleName::new("default");
let address = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let addresses = std::vec![address];
let empty = crate::SolanaInflationRewardConfig::default();
for config in [std::option::Option::None, std::option::Option::Some(&empty)] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.single_null.json"));
let pool = pool_for_url(url.as_str());
let rewards = pool.get_inflation_reward(&role, addresses.as_slice(), config).await.expect("single null inflation reward fixture must succeed");
assert_eq!(rewards, std::vec![std::option::Option::None]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([["11111111111111111111111111111111"]]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_accepts_empty_address_list_without_inventing_a_minimum() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.empty.json"));
let pool = pool_for_url(url.as_str());
let rewards = pool
.get_inflation_reward(&crate::HttpRoleName::new("default"), &[], std::option::Option::None)
.await
.expect("empty inflation reward fixture must succeed");
assert!(rewards.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([[]]));
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_does_not_invent_a_256_address_limit() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.many_nulls.json"));
let pool = pool_for_url(url.as_str());
let address = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let addresses = std::vec![address; 300];
let rewards = pool
.get_inflation_reward(&crate::HttpRoleName::new("default"), addresses.as_slice(), std::option::Option::None)
.await
.expect("300-address inflation reward fixture must succeed without local cardinality cap");
assert_eq!(rewards.len(), 300);
assert!(rewards.iter().all(std::option::Option::is_none));
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["params"][0].as_array().expect("address parameter must be an array").len(), 300);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_rejects_processed_commitment_before_io() {
let role = crate::HttpRoleName::new("default");
let pool = pool_for_url("http://127.0.0.1:1");
let address = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let config = crate::SolanaInflationRewardConfig::new(
std::option::Option::Some(912),
std::option::Option::Some(crate::SolanaCommitment::Processed),
std::option::Option::None,
);
let result = pool.get_inflation_reward(&role, &[address], std::option::Option::Some(&config)).await;
let error = result.expect_err("processed inflation-reward commitment must fail before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_rejects_response_cardinality_mismatch() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.single_null.json"));
let pool = pool_for_url(url.as_str());
let first = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let second = inflation_reward_fixture_pubkey("Vote111111111111111111111111111111111111111");
let result = pool.get_inflation_reward(&crate::HttpRoleName::new("default"), &[first, second], std::option::Option::None).await;
let error = result.expect_err("inflation reward response count mismatch must fail typed decoding");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
assert_eq!(error.context()[1].key(), "expected_count");
assert_eq!(error.context()[1].value(), "2");
assert_eq!(error.context()[2].key(), "actual_count");
assert_eq!(error.context()[2].value(), "1");
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_preserves_min_context_slot_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.error_min_context.json"));
let pool = pool_for_url(url.as_str());
let address = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let config = crate::SolanaInflationRewardConfig::new(
std::option::Option::Some(912),
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(431_500_000),
);
let result = pool.get_inflation_reward(&crate::HttpRoleName::new("default"), &[address], std::option::Option::Some(&config)).await;
let error = result.expect_err("inflation reward min-context RPC error must propagate");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_inflation_reward_preserves_epoch_rewards_period_active_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_inflation_reward.error_epoch_rewards_active.json"));
let pool = pool_for_url(url.as_str());
let address = inflation_reward_fixture_pubkey("11111111111111111111111111111111");
let result = pool.get_inflation_reward(&crate::HttpRoleName::new("default"), &[address], std::option::Option::None).await;
let error = result.expect_err("active epoch-rewards RPC error must propagate");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
}

View File

@@ -0,0 +1,191 @@
<!-- file: deltas/0.2.4/pre.001-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.001-fix.001` — audit SIMD Transport et correction des matrices du plan
## Base requise
Ce fix s'applique après :
```text
0.2.4-pre.001
workspace.package.version = "0.2.4-pre.1"
```
Le delta historique `deltas/0.2.4/pre.001.md` reste inchangé.
## Motif du fix
Deux corrections documentaires sont nécessaires avant `pre.002` :
1. les matrices Blocks/Economics du plan utilisaient des caractères `|` à l'intérieur de cellules Markdown, notamment dans des unions telles que `object | null`; certains renderers les interprètent comme des séparateurs de colonnes même lorsqu'ils sont placés dans du code inline ;
2. l'apparition de `commissionBps` liée à SIMD-0291 impose de vérifier systématiquement si d'autres SIMDs actuels ou proches de la baseline stable affectent la surface HTTP couverte par `KSP-TRANSPORT-007`.
Ce fix ne modifie aucun wrapper ni source Rust. Il complète le plan avant l'implémentation afin que les modifications nécessaires soient réalisées dans les prereleases fonctionnelles prévues.
## Audit SIMD `KSP-TRANSPORT-007`
Le critère retenu est le wire réellement supporté par la baseline Solana/Agave stable, pas le seul statut administratif d'un SIMD. Un SIMD encore en `Review` peut donc être pertinent si Agave stable expose déjà un champ de compatibilité ; inversement, un champ proposé mais absent d'Agave stable n'est pas inventé par KSP.
### SIMD à prendre en compte dans `0.2.4`
**SIMD-0118 — Partitioned Epoch Rewards Distribution**
- statut actuel : `Activated` ;
- impact HTTP direct : `getBlock.numRewardPartitions` ;
- Agave `v4.2.1` expose `UiConfirmedBlock.num_reward_partitions: Option<u64>` ;
- le futur DTO bloc KSP doit préserver la présence/omission du champ, sans synthétiser `0` ;
- le plan retient `SolanaWireField<u64>` ou une représentation wire équivalente et des fixtures `present`/`omitted`.
**SIMD-0291 — Commission Rate in Basis Points**
- statut actuel du document SIMD : `Review` ;
- Agave stable `v4.2.1` expose néanmoins déjà les champs RPC de compatibilité en basis points ;
- `getVoteAccounts.inflationRewardsCommissionBps` est déjà implémenté et testé par la surface `0.2.2` : aucune remédiation des 37 wrappers acquis ;
- `getBlock.rewards[].commissionBps` doit être conservé dans un reward wire de bloc dédié ;
- `getInflationReward[].commissionBps` doit être conservé dans le DTO d'inflation reward ;
- `commission` reste nullable et `commissionBps` reste optionnel/omis ; KSP ne dérive pas l'un depuis l'autre ;
- `getTransaction.meta` de `0.2.3` est déjà lossless en `serde_json::Value`, donc les rewards imbriqués ne perdent pas cette extension.
### SIMDs déjà couverts ou sans nouveau wire immédiat
**SIMD-0185 — Vote Account v4** est `Accepted`. Les évolutions des account parsers restent compatibles avec `SolanaParsedAccountData.parsed: serde_json::Value`. Le DTO stable `RpcVoteAccountInfo` d'Agave `v4.2.1` n'expose pas les collector fields de Vote Account v4 ; KSP ne les ajoute pas spéculativement à `SolanaVoteAccountInfo`.
**SIMD-0186 — Loaded Transaction Data Size Specification** est `Accepted`. `simulateTransaction.loadedAccountsDataSize` est déjà préservé dans `0.2.3` via `SolanaWireField<u32>` avec couverture de l'omission. Transport restitue cette valeur runtime et ne la recalcule pas.
**SIMD-0385 — Transaction V1 Format** est en `Review`. Le contrat KSP actuel est déjà future-compatible parce que `SolanaTransactionVersion::Number(u8)` n'est pas limité à la version `0` et les payloads JSON restent lossless. Le plan ajoute toutefois une canary avec une version numérique non nulle et impose la même propriété au wire `getBlock`.
**SIMD-0096 — Reward full priority fee to validator** est `Activated`, mais modifie la distribution économique sans modifier la forme HTTP consommée par Transport. Aucun calcul de distribution ne doit être réimplémenté côté KSP.
**SIMD-0550 — Double Disinflation Rate** est en `Review` et annonce explicitement une évolution des valeurs retournées par `getInflationRate` et `getInflationGovernor` sans changement d'API. Les DTOs KSP restent des valeurs runtime et Transport ne réimplémente aucune formule d'inflation.
### Watchlist obligatoire
**SIMD-0180 — Vote Account Address Keyed Leader Schedule** est en `Review`. Il demande que les endpoints RPC existants de leader schedule continuent à retourner l'identité validator pour compatibilité et prévoit de nouveaux endpoints vote-account-keyed. Le wrapper `getLeaderSchedule` existant ne change donc pas ; le réaudit final de l'inventaire doit en revanche détecter si de nouveaux endpoints sont devenus officiels.
**SIMD-0307 — Add Block Footer** est en `Review` et propose une option `footer` ainsi que de nouveaux champs dans `getBlock`. **SIMD-0298 — Add `bank_hash` to block footer** est encore au statut `Idea` et prévoit d'étendre ce footer avec `bank_hash`. Ces éléments sont absents de la baseline stable Agave `v4.2.1`. Ils ne sont donc pas ajoutés maintenant, mais le plan impose un réaudit conjoint avant `getBlock` et à la compliance finale. S'ils deviennent stables pendant `0.2.4`, `KSP-TRANSPORT-007` imposera config, wire et fixtures pour la totalité des champs livrés.
**SIMD-0490 — Upgrade BPF Stake Program to v5.0.0** est en `Review` et prévoit une évolution du minimum de délégation. `getStakeMinimumDelegation` doit restituer la valeur runtime contextualisée sans coder en dur `1 lamport`, `1 SOL` ou une autre valeur. Réaudit prévu avant la tranche Economics concernée et à la clôture.
**SIMD-0553 — Base Inclusion and Resource-based Fee** est en `Draft`. Il modifie la sémantique des totals de fee pour `getFeeForMessage`, `simulateTransaction` et `getTransaction.meta.fee` mais n'impose actuellement pas de nouveau JSON wire. Les wrappers acquis restent pass-through/lossless ; la compliance finale réaudite ce point si le schéma stable évolue.
**SIMD-0123 — Block Revenue Sharing** reste en `Review`. Il peut modifier à terme la provenance/sémantique des rewards sans ajouter aujourd'hui un champ HTTP stable dans `v4.2.1`. KSP ne crée donc aucune taxonomie économique spéculative dans le reward wire.
## Modifications désormais prévues par le plan
Les prereleases fonctionnelles devront notamment prévoir :
```text
getBlock
confirmed block dédié
numRewardPartitions préservé via SolanaWireField<u64> ou équivalent (SIMD-0118)
reward de bloc dédié
commission nullable
commissionBps optionnel/omis (SIMD-0291)
transaction version Number(u8) non limitée à 0 (canary SIMD-0385)
réaudit conjoint SIMD-0298/0307 avant implémentation finale du contrat
getInflationReward
reward d'inflation distinct du reward de bloc
commission nullable
commissionBps optionnel/omis (SIMD-0291)
getStakeMinimumDelegation
valeur runtime contextualisée
aucun minimum local codé en dur
réaudit SIMD-0490
compliance pre.009
réaudit SIMD HTTP au minimum 0180 / 0298 / 0307 / 0385 / 0490 / 0550 / 0553
réaudit de l'inventaire officiel pour détecter de nouveaux endpoints
vérification qu'aucune évolution devenue stable pendant la session n'est omise
```
Aucune nouvelle dépendance n'est requise par ces décisions.
## Correction des matrices Markdown
Les deux matrices du plan ont été réécrites avec les mêmes quatre largeurs de colonnes afin d'être alignées dans la source Markdown.
Les unions textuelles qui contenaient un pipe interne ont été remplacées par `ou`, notamment :
```text
object ou null
array<u64> ou null
i64 ou null
commission: u8 ou null
```
Le contrôle statique du fichier exige désormais exactement cinq caractères `|` par ligne de tableau : un délimiteur initial, trois séparateurs de colonnes et un délimiteur final. Il n'existe plus de pipe de données à l'intérieur d'une cellule.
## Version Cargo
Ce fix est exclusivement documentaire. Conformément à la règle de versioning des fixes documentaires, le signal technique reste :
```text
workspace.package.version = "0.2.4-pre.1"
```
Aucune modification de `Cargo.toml` n'est effectuée.
## Fichier modifié
```text
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
```
Le header documentaire du plan passe de `version: 1` à `version: 2`.
## Fichier ajouté
```text
deltas/0.2.4/pre.001-fix.001.md
```
## Fichiers volontairement inchangés
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
deltas/0.2.4/pre.001.md
crates/ksp-onchain-transport-lib/src/**
crates/ksp-onchain-transport-lib/unit_tests/**
crates/ksp-onchain-transport-lib/tests/**
```
Les structures/fonctions Rust requises par l'audit sont planifiées pour les tranches fonctionnelles de `0.2.4`; elles ne sont pas introduites artificiellement dans ce fix documentaire d'ouverture.
## Validations exécutées
- réaudit primaire du repository SIMD courant et de la baseline Agave stable `v4.2.1` ;
- confirmation de SIMD-0118 `Activated` et de son lien avec `numRewardPartitions` ;
- confirmation dans Agave `v4.2.1` du champ `UiConfirmedBlock.num_reward_partitions` ;
- confirmation dans Agave `v4.2.1` de `Reward.commission_bps` et `RpcInflationReward.commission_bps` ;
- confirmation que `RpcVoteAccountInfo.inflation_rewards_commission_bps` est déjà couvert dans KSP `0.2.2` ;
- confirmation que `SolanaTransactionVersion::Number(u8)` et les payloads transaction/meta lossless de `0.2.3` n'imposent pas de remédiation SIMD-0385 ;
- confirmation que SIMD-0180 est encore `Review`, que les RPC leader schedule existants doivent conserver l'identité validator, et que les futurs endpoints vote-account-keyed relèvent d'une évolution d'inventaire ;
- confirmation que SIMD-0186 est `Accepted` et que `loadedAccountsDataSize` est déjà couvert losslessly dans `simulateTransaction` ;
- confirmation que SIMD-0298 est encore `Idea`, dépend du block footer de SIMD-0307 et ne justifie aucun champ `bankHash` spéculatif dans la baseline actuelle ;
- confirmation que SIMD-0307 est encore `Review` et que ses champs footer sont absents des types stables Agave `v4.2.1` ;
- confirmation que SIMD-0490 est encore `Review` et que le wrapper KSP ne doit pas figer une valeur de minimum delegation ;
- confirmation que SIMD-0550 est encore `Review` et déclare une évolution de valeurs inflation sans changement d'API ;
- confirmation que SIMD-0553 est encore `Draft` et n'impose pas actuellement de nouvelle forme JSON ;
- contrôle des deux matrices : quatre colonnes et exactement cinq délimiteurs `|` par ligne ;
- contrôle que le delta `pre.001.md` historique reste inchangé.
## Validations Cargo
Aucune source Rust, configuration runtime, dépendance ou version Cargo n'est modifiée par ce fix. Les validations Cargo de `pre.001` restent celles à effectuer par l'opérateur avant commit ; ce fix n'en revendique aucune nouvelle.
## Commit attendu
Après application et validation documentaire :
```text
v0.2.4-pre.001-fix.001
```
## Suite
`0.2.4-pre.002` peut démarrer les primitives/configs/results communs en intégrant dès leur conception les exigences SIMD-0118 et SIMD-0291 fixées ci-dessus, avec les watchpoints SIMD explicitement reportés aux tranches concernées.

210
deltas/0.2.4/pre.001.md Normal file
View File

@@ -0,0 +1,210 @@
<!-- file: deltas/0.2.4/pre.001.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.001` — audit Blocks/Economics, compliance cible et sizing
## Base requise
Release stable attendue :
```text
v0.2.3
```
L'archive KSP fournie porte `workspace.package.version = "0.2.3"`. Elle ne contient pas `.git`; le tag `v0.2.3` n'est donc pas revérifiable localement depuis le zip.
## Objectif
Ouvrir `0.2.4` sans implémentation fonctionnelle lourde : relire les contrats KSP, réauditer la documentation Solana actuelle et la génération Agave stable, confirmer les 10 méthodes Blocks + 5 Economics, cadrer les overloads/legacy/DTOs/wires et contraintes `KSP-TRANSPORT-007`, puis trancher le gate de sizing avant les modifications Rust.
## Version Cargo
Conformément à `VER-ID-009`, la prerelease non-fix synchronise le signal technique :
```text
0.2.3 -> 0.2.4-pre.1
```
Aucune source Rust, dépendance Cargo ou configuration runtime n'est modifiée dans cette tranche.
## Résultats principaux
- l'index HTTP Solana courant conserve **52 méthodes courantes** ;
- les catégories Blocks et Economics restent exactement **10 + 5** méthodes, sans changement de partition ;
- la navigation Deprecated conserve les **14 méthodes historiques** déjà enregistrées par KSP ;
- les 15 descriptors `HttpRpcCoverageRelease::V0_2_4` présents dans `v0.2.3` correspondent exactement à l'inventaire officiel actuel et restent `Read / RetrySafe` ;
- `getBlock` reste `StableWithDeprecatedLegacy` parce que son second paramètre accepte toujours un bare encoding legacy en plus de l'objet de config moderne ;
- la documentation Solana actuelle lie encore ses sources RPC à Agave `v3.1.8`; elle doit donc être cross-auditée contre la génération stable réellement courante ;
- au 2026-08-18, la baseline runtime retenue est **Agave `v4.2.1`** dans la branche stable `4.2.x`; `v4.3.0-beta.0` reste une prerelease ;
- `getBlock` accepte la config moderne complète `commitment/encoding/transactionDetails/maxSupportedTransactionVersion/rewards`, conserve la forme bare encoding et retourne `object|null` ;
- les transactions de bloc partagent les primitives `SolanaEncodedTransaction`, `SolanaTransactionVersion` et `SolanaWireField<T>` avec `0.2.3`, mais ne doivent pas réutiliser `SolanaConfirmedTransaction` tel quel car ce DTO contient des champs top-level propres à `getTransaction` ;
- `getBlocks` conserve les overloads `[start]`, `[start,end]`, `[start,config]`, `[start,end,config]`; une différence `end-start > 500_000` est invalide, tandis que `end < start` produit un tableau vide ;
- `getBlocksWithLimit` conserve `limit <= 500_000`; `limit = 0` est valide ;
- `getRecentPerformanceSamples` conserve `720` comme défaut **et** maximum ;
- `getBlockProduction` conserve `identity`, `range.firstSlot`, `range.lastSlot?`, `commitment`, une réponse contextualisée et l'invariant déterministe `lastSlot >= firstSlot` ;
- `getInflationReward` conserve l'ordre/cardinalité et les `null` positionnels ; Agave `v4.2.1` expose en plus de `commission` le champ optionnel `commissionBps: u16`, absent de la page publique actuelle, qui doit être préservé par KSP ;
- aucune limite sémantique fixe d'adresses `getInflationReward` n'a été établie par les sources courantes : KSP n'en invente pas une à partir de la limite générale de taille de requête ;
- `getSupply` conserve `excludeNonCirculatingAccountsList` et le résultat contextualisé `{total,circulating,nonCirculating,nonCirculatingAccounts}` ;
- aucune nouvelle dépendance n'est nécessaire pour cette release.
## Gate de sizing
Question obligatoire :
```text
Les 15 wrappers Blocks/Economics, leurs DTOs/wires, les overloads/legacy,
les tests, la compliance 52+14 et la documentation peuvent-ils être
clôturés proprement dans cette release/session ?
```
Réponse :
```text
OUI.
```
Aucun split de release n'est nécessaire. La complexité sera contrôlée en isolant `getBlock`, `getBlockProduction`, `getInflationReward` et la compliance finale dans des tranches dédiées. Une prerelease supplémentaire sera créée si une tranche dépasse le budget effectif d'environ 1520 minutes ; aucune méthode ne sera reportée silencieusement hors de `0.2.4`.
## Décisions de conception
### Réutilisation wire
Réutiliser directement :
```text
SolanaCommitment
SolanaCommitmentConfig
SolanaContextConfig
SolanaRpcContext
SolanaRpcResponse<T>
SolanaTransactionEncoding
SolanaEncodedTransaction
SolanaTransactionVersion
SolanaWireField<T>
```
Créer des DTOs Blocks/Economics seulement lorsque le contrat wire est réellement distinct.
### `getBlock`
Le résultat de bloc aura un type dédié. Les entrées `transactions[]` composent les primitives Transaction existantes mais ne réutilisent pas `SolanaConfirmedTransaction` tel quel. Les champs conditionnels issus de `transactionDetails`/`rewards` doivent conserver leurs omissions et `null` significatifs.
### Contraintes déterministes avant I/O
Retenues :
```text
getBlock commitment >= confirmed
getBlocks commitment >= confirmed
getBlocks end-start <= 500_000 lorsque end >= start
getBlocksWithLimit commitment >= confirmed
getBlocksWithLimit limit <= 500_000
getRecentPerformanceSamples limit <= 720
getBlockProduction lastSlot >= firstSlot lorsque les deux sont présents
```
Ne pas transformer en validations locales les erreurs dépendant de l'état du ledger/runtime.
### Dépendances
```text
nouvelle dépendance NON
solana-rpc-client/client NON
base64/bs58 NON
```
## Prévision de travail
```text
pre.001 audit officiel + Agave stable + matrice + wires + contraintes + sizing
pre.002 primitives/configs/results Blocks/Economics partagés + fixtures communes
pre.003 5 Blocks simples
pre.004 getBlocks + getBlocksWithLimit + getRecentPerformanceSamples
pre.005 getBlockProduction
pre.006 getBlock moderne + legacy + transactionDetails + wire riche
pre.007 4 Economics simples
pre.008 getInflationReward + commissionBps + null positionnels
pre.009 compliance finale 52/52 + 14/14 + docs + smokes + graphes + prompt 0.2.5
rel.001 publication stricte
```
Le plan détaillé est `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`.
## Fichiers ajoutés
```text
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
deltas/0.2.4/pre.001.md
```
## Fichiers modifiés
```text
Cargo.toml
ROADMAP.md
docs/000-README.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
```
## Fichiers supprimés
Aucun.
## Fichiers volontairement inchangés
```text
CHANGELOG.md
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/**
crates/ksp-onchain-transport-lib/unit_tests/**
crates/ksp-onchain-transport-lib/tests/**
deltas/0.2.1/**
deltas/0.2.2/**
deltas/0.2.3/**
```
Le changelog reste synchronisé à la phase documentaire finale conformément à `VER-SESSION-005`. Les deltas historiques ne sont jamais réécrits.
## Validations exécutées
- relecture des sources internes obligatoires du prompt `0.2.4` ;
- inspection de l'archive stable KSP fournie et de la surface Transport `v0.2.3` ;
- confirmation locale de `workspace.package.version = 0.2.3` avant ouverture ;
- confirmation locale du registry `52 current / 14 historical` et de la partition `4 / 22 / 11 / 15` ;
- confirmation locale que les 15 descriptors `V0_2_4` sont exactement les 10 Blocks + 5 Economics attendues et sont `Read / RetrySafe` ;
- inspection des wires partagés `rpc_common` et `rpc_transactions` ainsi que des canaries de release ;
- consultation de l'index HTTP Solana officiel courant, du JSON Structures Reference et des 15 pages Blocks/Economics ;
- consultation de la navigation Deprecated officielle courante ;
- confirmation que les liens source de la documentation publique pointent encore vers Agave `v3.1.8` ;
- cross-audit primaire d'Agave `v4.2.1` : release stable, `rpc/src/rpc.rs`, `rpc-client-types/src/config.rs` et `response.rs` ;
- confirmation des overloads `getBlocks`, limites 500_000/720, contrainte de range `getBlockProduction` et extension `commissionBps` de `getInflationReward` ;
- contrôle qu'aucune nouvelle dépendance n'est requise par le plan.
## Validations non exécutées
Le sandbox courant ne fournit pas le binaire `cargo`; les validations Cargo ne peuvent donc pas être exécutées ici et doivent être rejouées par l'opérateur avant commit :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Aucun `cargo tree` n'est nécessaire dans `pre.001` puisque les dépendances/features ne changent pas. Les graphes complets restent obligatoires à la clôture `pre.009`.
L'archive fournie ne contient pas `.git`; le commit attendu après application et validations suit `VER-GIT-001` :
```text
v0.2.4-pre.001
```
## Questions ouvertes
Aucune question bloquante pour `pre.002`.
## Suite
`0.2.4-pre.002` : introduire les primitives/configs/results Blocks/Economics partagés et les fixtures déterministes communes, sans encore concentrer toute la logique de `getBlock` dans la même tranche.

View File

@@ -0,0 +1,159 @@
<!-- file: deltas/0.2.4/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.002-fix.001` — suppression du code interne prématurément actif
## Base requise
Livraison précédente :
```text
0.2.4-pre.002
workspace.package.version = "0.2.4-pre.2"
```
Ce fix corrige uniquement les warnings `dead_code` introduits par `pre.002` dans les nouveaux modules Blocks/Economics. Il ne redéfinit pas le périmètre fonctionnel de la prerelease et ne modifie ni les DTOs publics, ni les fixtures, ni les tests, ni le registre RPC.
## Problème observé
Les validations locales de `pre.002` ont montré que la tranche est fonctionnellement verte :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé avec 29 warnings dead_code
cargo clippy --workspace --all-targets -> terminé avec les mêmes 29 warnings dead_code
cargo test -p ksp-onchain-transport-lib -> 196 unit tests OK
20 public API tests OK
15 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Les warnings proviennent exclusivement de helpers internes d'encodage/décodage et de structs `Wire*` préparés en `pre.002` pour les futurs wrappers, alors qu'aucun wrapper `V0_2_4` ne les consomme encore dans une compilation normale de la bibliothèque.
Le choix `#[allow(dead_code)]` est rejeté : le lint est correct et signale du code de production activé trop tôt.
## Correction
Les 17 contrats publics ajoutés par `pre.002` restent compilés et exportés normalement.
Seuls les éléments internes qui n'ont encore aucun consommateur runtime sont placés sous `#[cfg(test)]` :
- helpers `is_empty` des configs Blocks/Economics ;
- helpers `to_json_value` des configs/ranges Blocks/Economics ;
- décodeurs internes `decode_wire` des résultats Blocks/Economics ;
- helpers privés de décodage des transactions/rewards de bloc ;
- structs privées `WireBlock*`, `WirePerformanceSample` et `WireInflation*`/`WireSupply`.
Ils restent donc compilés par les unit tests de `pre.002`, qui continuent à valider les formes wire et les fixtures déterministes, mais ils ne participent plus au build normal de la crate tant qu'un wrapper runtime ne les utilise pas.
Cette approche garde le découpage des prereleases strict :
```text
pre.002 -> contrats publics + fixtures + validation test-only des helpers wire
pre.003+ -> activation progressive des helpers internes au moment où leur wrapper runtime existe
```
À partir de `pre.003`, `SolanaBlockCommitment::decode_wire` sera sorti de `#[cfg(test)]` avec l'implémentation de `getBlockCommitment`. Les autres encodeurs/décodeurs seront activés uniquement dans la prerelease qui introduit leur consommateur.
Aucun `#[allow(dead_code)]` n'est ajouté.
## Version Cargo
Ce correctif modifie des sources Rust participant au build. Conformément à `VER-ID-007` et `VER-ID-010` :
```text
0.2.4-pre.2 -> 0.2.4-pre.2.fix.1
```
Aucune dépendance ni feature Cargo n'est modifiée.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
```
## Fichiers ajoutés
```text
deltas/0.2.4/pre.002-fix.001.md
```
## Fichiers supprimés
Aucun.
## Fichiers volontairement inchangés
```text
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/fixtures/http/**
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
ROADMAP.md
CHANGELOG.md
deltas/0.2.4/pre.002.md
```
Aucun delta historique n'est réécrit.
## Validations exécutées dans le sandbox
- contrôle différentiel contre la livraison `pre.002` ;
- vérification que seuls les trois fichiers de production listés ci-dessus sont modifiés ;
- contrôle que les 17 types publics de `pre.002` restent hors `#[cfg(test)]` ;
- contrôle que tous les helpers et structs privés signalés `dead_code` par les validations locales sont maintenant test-only ;
- contrôle de l'absence de `#[allow(dead_code)]` ;
- contrôle de l'absence de `unwrap`, `expect`, `panic!` et `unsafe` dans les lignes modifiées ;
- `git diff --check` sur le fix ;
- contrôle des versions de fichiers modifiés.
## Validations Cargo non exécutées dans le sandbox
Le sandbox ne fournit pas `cargo`, `rustc` ni `rustfmt`. Les validations du fix doivent donc être rejouées sur le checkout de développement :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Le critère de clôture du fix est plus strict que pour la tentative initiale : `cargo check` et `cargo clippy` doivent terminer sans les 29 warnings `dead_code` introduits par `pre.002`.
Le commit attendu après validation est :
```text
v0.2.4-pre.002-fix.001
```
## Décisions prises
- conserver les DTOs publics actifs en production ;
- rendre test-only uniquement l'infrastructure wire interne sans consommateur runtime ;
- ne pas masquer les warnings avec `allow(dead_code)` ;
- activer progressivement les helpers lors de l'arrivée de chaque wrapper ;
- ne pas modifier le plan `011`, le périmètre fonctionnel restant identique.
## Questions ouvertes
Aucune.
## Suite
Après validation du fix, `0.2.4-pre.003` reste inchangé :
```text
getBlockCommitment
getBlockHeight
getBlockTime
getFirstAvailableBlock
minimumLedgerSlot
```

331
deltas/0.2.4/pre.002.md Normal file
View File

@@ -0,0 +1,331 @@
<!-- file: deltas/0.2.4/pre.002.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.002` — primitives Blocks/Economics et fixtures wire partagées
## Base requise
Livraison précédente :
```text
0.2.4-pre.001-fix.001
workspace.package.version = "0.2.4-pre.1"
```
Le plan canonique est `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` version 2. Le fix `pre.001-fix.001` est appliqué avant cette tranche ; les deltas historiques `pre.001.md` et `pre.001-fix.001.md` restent inchangés.
## Objectif
Installer les primitives, configurations et résultats partagés nécessaires aux 10 wrappers Blocks et 5 wrappers Economics de `0.2.4`, ainsi que leurs fixtures wire locales déterministes, sans encore implémenter les wrappers prévus à partir de `pre.003` et sans déclarer la couverture typed `V0_2_4` complète.
Cette tranche matérialise dès maintenant les exigences `KSP-TRANSPORT-007` qui appartiennent au wire partagé : omissions/null explicites, versions de transaction numériques génériques, reward partitions SIMD-0118, commissions basis-points SIMD-0291, adresses RPC typées en `Pubkey` et sous-arbres Transaction riches conservés losslessly.
## Version Cargo
Conformément à `VER-ID-009`, la nouvelle prerelease synchronise le signal technique :
```text
0.2.4-pre.1 -> 0.2.4-pre.2
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## Implémentation
### Module Blocks partagé
Le nouveau module privé `rpc_blocks` ajoute les types publics suivants, réexportés explicitement depuis la racine de crate :
```text
SolanaTransactionDetails
SolanaGetBlockConfig
SolanaBlockProductionRange
SolanaBlockProductionConfig
SolanaBlockCommitment
SolanaBlockProductionResultRange
SolanaBlockProduction
SolanaBlockReward
SolanaBlockTransaction
SolanaConfirmedBlock
SolanaPerformanceSample
```
`SolanaTransactionDetails` couvre exactement les quatre valeurs wire modernes :
```text
full
signatures
none
accounts
```
`SolanaGetBlockConfig` conserve indépendamment les cinq options du contrat moderne :
```text
commitment
encoding
transactionDetails
maxSupportedTransactionVersion
rewards
```
Aucune validation de commitment ni aucun choix de forme legacy n'est encore exécuté ici : ces responsabilités appartiennent au wrapper `getBlock` de `pre.006`.
`SolanaBlockProductionConfig` conserve `commitment`, une `identity` typée `Pubkey` et une range `firstSlot/lastSlot?`. La validation `lastSlot >= firstSlot` reste volontairement réservée au wrapper `getBlockProduction` de `pre.005`.
`SolanaBlockProduction` décode les clés dynamiques de `byIdentity` en `Pubkey` et conserve les couples `(leader slots, blocks produced)` ainsi que la range effective retournée par le runtime. Le futur wrapper appliquera le `SolanaRpcResponse<T>` partagé autour de ce résultat.
### Wire `getBlock`
`SolanaConfirmedBlock` est un DTO bloc dédié et ne réutilise pas `SolanaConfirmedTransaction`, dont `slot` et `blockTime` sont propres au top-level de `getTransaction`.
Le bloc conserve :
```text
previousBlockhash
blockhash
parentSlot
transactions
signatures
rewards
numRewardPartitions
blockTime
blockHeight
```
Les champs conditionnels `transactions`, `signatures`, `rewards` et `numRewardPartitions` utilisent `SolanaWireField<T>` afin de préserver les trois états :
```text
Omitted
Null
Value(T)
```
`numRewardPartitions` couvre explicitement le wire associé à SIMD-0118 sans synthétiser une valeur `0` en cas d'omission ou de `null`.
`SolanaBlockTransaction` compose les primitives Transaction déjà acquises :
```text
SolanaEncodedTransaction
SolanaWireField<serde_json::Value> pour meta
SolanaWireField<SolanaTransactionVersion> pour version
```
Le décodeur interne `SolanaTransactionVersion::decode_wire` passe donc de privé à `pub(crate)` pour être réutilisé sans dupliquer la logique `legacy | u8`. Son comportement public reste inchangé. Une fixture `getBlock` porte volontairement la version numérique `1` afin d'empêcher une future régression qui bornerait artificiellement cette primitive à V0, conformément au watchpoint SIMD-0385.
`SolanaBlockReward` conserve :
```text
pubkey: Pubkey
lamports: i64
postBalance: u64
rewardType: String ou null
commission: u8 ou null
commissionBps: SolanaWireField<u16>
```
`commissionBps` couvre l'extension stable déjà exposée par le runtime pour SIMD-0291. `commission` et `commissionBps` restent indépendants ; Transport ne dérive jamais l'un depuis l'autre.
`rewardType` reste volontairement une chaîne nullable au niveau Transport. Cette tranche n'introduit pas une taxonomie économique KSP spéculative alors que les évolutions de reward semantics restent dans la watchlist, notamment SIMD-0123.
### Module Economics partagé
Le nouveau module privé `rpc_economics` ajoute et réexporte :
```text
SolanaInflationGovernor
SolanaInflationRate
SolanaInflationRewardConfig
SolanaInflationReward
SolanaSupplyConfig
SolanaSupply
```
`SolanaInflationGovernor` et `SolanaInflationRate` conservent directement les valeurs runtime `f64`/epoch sans reproduire localement les formules d'inflation.
`SolanaInflationRewardConfig` sérialise uniquement les options fournies :
```text
epoch
commitment
minContextSlot
```
`SolanaInflationReward` conserve `commission: Option<u8>` et `commissionBps: SolanaWireField<u16>` pour couvrir SIMD-0291 sans perdre la différence omission/null/présent du champ basis-points. La cardinalité et les `null` positionnels de la liste `getInflationReward` seront pris en charge par le wrapper de `pre.008`; le DTO de cette tranche représente uniquement une entrée non-null.
`SolanaSupplyConfig` utilise `Option<bool>` pour `excludeNonCirculatingAccountsList`. Cela distingue un champ omis d'un `false` explicitement envoyé, tout en laissant le runtime appliquer son défaut lorsque l'option est absente.
`SolanaSupply` convertit la liste ordonnée `nonCirculatingAccounts` en `Vec<Pubkey>` et échoue proprement si une clé retournée n'est pas une public key valide. Le futur wrapper réutilisera `SolanaRpcResponse<SolanaSupply>`.
### Frontières conservées
Cette prerelease n'ajoute aucun `impl HttpTransportPool` pour les 15 méthodes `V0_2_4`. Les nouveaux types sont donc uniquement des contrats wire/config/result partagés.
Le flux d'exécution reste inchangé :
```text
wrapper typed futur
-> descriptor central
-> execute_standard_rpc
-> pool/admission
-> executor HTTP
-> validation JSON-RPC
-> decode typed
```
Aucun client HTTP parallèle, aucune boucle de retry locale et aucune dépendance Solana RPC haut niveau ne sont introduits.
## Fixtures déterministes ajoutées
```text
crates/ksp-onchain-transport-lib/fixtures/http/block_commitment.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/block_production.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.omissions.json
crates/ksp-onchain-transport-lib/fixtures/http/performance_sample.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_governor.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_rate.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_reward.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/supply.v4_2_1.json
```
Elles couvrent notamment :
- block commitment présent et `null` ;
- `getBlockProduction.byIdentity` et la range effective ;
- bloc riche avec transaction version numérique `1` ;
- `meta` explicite `null` et version omise ;
- reward `commissionBps` présent puis omis ;
- `numRewardPartitions` présent, omis et explicitement `null` ;
- performance sample courant puis ancien sans `numNonVoteTransactions` ;
- inflation governor/rate ;
- liste inflation reward avec un `null` positionnel et les variantes `commissionBps` ;
- supply avec liste ordonnée de public keys.
## Tests ajoutés
`unit_tests/rpc_blocks.rs` ajoute **8 tests déterministes** couvrant configs, résultats, omissions/null, version transaction non nulle, extensions SIMD et validation des public keys.
`unit_tests/rpc_economics.rs` ajoute **6 tests déterministes** couvrant configs, inflation values, reward basis-points, `null` positionnel de fixture, supply ordonnée et invalid pubkey.
Les tests d'intégration ajoutent aussi :
- une canarie de surface publique qui vérifie que les 17 nouveaux types sont disponibles depuis la racine de crate ;
- une canarie de registre qui confirme que `V0_2_4` reste exactement composé des **10 Blocks + 5 Economics**, tous `Read / RetrySafe`, et que `getBlock` conserve son marqueur de forme legacy dépréciée sans prétendre que les wrappers existent déjà.
La canarie typed-complete `V0_2_4 == 15` reste volontairement fermée jusqu'aux tranches d'implémentation des wrappers.
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
crates/ksp-onchain-transport-lib/fixtures/http/block_commitment.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/block_production.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.omissions.json
crates/ksp-onchain-transport-lib/fixtures/http/performance_sample.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_governor.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_rate.v4_2_1.json
crates/ksp-onchain-transport-lib/fixtures/http/inflation_reward.variants.json
crates/ksp-onchain-transport-lib/fixtures/http/supply.v4_2_1.json
deltas/0.2.4/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
```
## Fichiers supprimés
Aucun.
## Fichiers volontairement inchangés
```text
CHANGELOG.md
ROADMAP.md
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
crates/ksp-onchain-transport-lib/Cargo.toml
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/executor.rs
crates/ksp-onchain-transport-lib/src/rpc_method.rs
crates/ksp-config-lib/**
config/**
deltas/0.2.4/pre.001.md
deltas/0.2.4/pre.001-fix.001.md
```
Aucun ancien delta n'est réécrit. Aucun wrapper Blocks/Economics n'est avancé prématurément.
## Validations exécutées dans le sandbox
- reconstruction exacte de la base `v0.2.3` puis application des overlays `0.2.4-pre.001` et `0.2.4-pre.001-fix.001` ;
- contrôle différentiel de la tranche `pre.002` contre cette base ;
- recoupement ciblé des configs/résultats avec la baseline Agave `v4.2.1` retenue par le plan ;
- parse JSON local des neuf nouvelles fixtures ;
- contrôle statique des 17 réexports crate-root ;
- contrôle statique que chaque nouveau item `pub`/`pub(crate)` de production possède sa documentation ;
- contrôle statique de l'absence de `impl HttpTransportPool` dans les nouveaux modules ;
- contrôle statique de l'absence de `use`, `unwrap`, `expect`, `panic!` et opérateur `?` dans les deux nouvelles sources de production ;
- contrôle statique des lignes Rust modifiées à `<= 160` colonnes ;
- contrôle différentiel de `Cargo.toml` : seul `workspace.package.version` change ;
- contrôle différentiel de `rpc_transactions.rs` : seule la version de fichier et la visibilité interne documentée de `SolanaTransactionVersion::decode_wire` changent ;
- contrôle que `CHANGELOG.md`, `ROADMAP.md`, le plan `011` version 2 et les deltas historiques restent inchangés.
## Validations Cargo non exécutées
Le sandbox courant ne fournit pas `cargo`, `rustc` ni `rustfmt`. Les commandes suivantes ne sont donc pas déclarées comme réussies et doivent être exécutées sur le checkout de développement avant commit :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Aucun `cargo tree` supplémentaire n'est requis par cette tranche puisqu'aucune dépendance ni feature n'a changé. Les graphes complets restent obligatoires à la clôture `pre.009`.
Le commit attendu après application et validations suit `VER-GIT-001` :
```text
v0.2.4-pre.002
```
## Décisions prises
- séparer physiquement les primitives Blocks et Economics dans deux modules Transport dédiés ;
- ne pas avancer de wrapper avant sa tranche propriétaire ;
- réutiliser `SolanaWireField<T>`, `SolanaEncodedTransaction` et `SolanaTransactionVersion` plutôt que dupliquer le wire Transaction ;
- ouvrir uniquement la visibilité interne de `SolanaTransactionVersion::decode_wire` sans modifier son API publique ;
- typer les identités/adresses RPC en `Pubkey` lorsqu'elles sont structurelles ;
- garder les sous-arbres Transaction/meta riches lossless via `serde_json::Value` ;
- garder `rewardType` ouvert au niveau Transport au lieu d'introduire une taxonomie économique prématurée ;
- distinguer `excludeNonCirculatingAccountsList` omis d'un `false` explicite ;
- intégrer dès les fixtures partagées SIMD-0118, SIMD-0291 et la canarie future-compatible SIMD-0385 ;
- ne pas modifier le plan `011`, aucune capacité stable additive n'ayant été découverte pendant cette tranche.
## Questions ouvertes
Aucune question bloquante pour `pre.003`.
## Suite
`0.2.4-pre.003` : implémenter les cinq wrappers Blocks simples avec requêtes JSON exactes et réponses typed :
```text
getBlockCommitment
getBlockHeight
getBlockTime
getFirstAvailableBlock
minimumLedgerSlot
```

378
deltas/0.2.4/pre.003.md Normal file
View File

@@ -0,0 +1,378 @@
<!-- file: deltas/0.2.4/pre.003.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.003` — cinq wrappers Blocks simples
## Base requise
Livraison précédente :
```text
0.2.4-pre.002-fix.001
workspace.package.version = "0.2.4-pre.2.fix.1"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 196 unit tests OK
20 public API tests OK
15 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Le plan canonique reste `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` version 2.
## Objectif
Implémenter exactement la première tranche de wrappers Blocks prévue par le plan :
```text
getBlockCommitment
getBlockHeight
getBlockTime
getFirstAvailableBlock
minimumLedgerSlot
```
Les cinq méthodes sont déjà présentes dans le registre audité `V0_2_4`, toutes classées `Read / RetrySafe`. Cette prerelease ne modifie donc pas le registre ; elle matérialise leurs wrappers typed au-dessus du chemin Transport commun existant.
Les méthodes Blocks restantes restent hors de cette tranche :
```text
getBlock
getBlockProduction
getBlocks
getBlocksWithLimit
getRecentPerformanceSamples
```
Les cinq méthodes Economics restent également sans wrapper runtime à ce stade.
## Réaudit RPC de la tranche
Le contrat HTTP courant a été revérifié avant implémentation :
- `getBlockCommitment` prend un `slot: u64` et retourne un objet avec `commitment: array<u64> ou null` et `totalStake: u64` ;
- `getBlockHeight` accepte un objet optionnel `commitment` / `minContextSlot` et retourne `u64` ;
- `getBlockTime` prend un `slot: u64` et retourne un timestamp Unix `i64` lorsqu'il est disponible ; KSP conserve également le résultat JSON `null` sous `Option<i64>` conformément au wire déjà audité ;
- `getFirstAvailableBlock` ne prend aucun paramètre et retourne un `u64` ;
- `minimumLedgerSlot` ne prend aucun paramètre et retourne un `u64`.
Aucun nouveau champ, overload ou invariant déterministe n'a été identifié pour ces cinq méthodes par rapport au plan `pre.001`.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.2.fix.1 -> 0.2.4-pre.3
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## Implémentation
### Chemin Blocks commun
`rpc_blocks.rs` ajoute un `impl HttpTransportPool` dédié aux wrappers Blocks. Tous les appels passent par :
```text
wrapper typed Blocks
-> blocks_descriptor(...)
-> execute_standard_rpc(...)
-> pool/admission
-> executor HTTP
-> validation JSON-RPC
-> decode typed
```
`blocks_descriptor` exige simultanément :
```text
category == HttpRpcCategory::Blocks
coverage_release == HttpRpcCoverageRelease::V0_2_4
```
Un descripteur absent ou incohérent échoue donc avant l'I/O normale au lieu de contourner le registre audité.
Aucune boucle de retry locale n'est ajoutée. Les cinq méthodes héritent de la politique centrale `RetrySafe` déjà attachée à leurs descripteurs.
### `getBlockCommitment`
Signature publique :
```text
HttpTransportPool::get_block_commitment(role, slot)
-> Result<SolanaBlockCommitment>
```
Paramètres wire :
```json
[430000123]
```
Le DTO `SolanaBlockCommitment` introduit en `pre.002` est désormais réellement consommé par un wrapper runtime. Son helper `decode_wire` et la struct privée `WireBlockCommitment` sortent donc de `#[cfg(test)]`.
Le champ nullable `commitment` reste représenté par `Option<Vec<u64>>`; `totalStake` reste `u64`.
Conformément au fix précédent, **aucun autre** décodeur Blocks/Economics préparé en `pre.002` n'est activé prématurément.
### `getBlockHeight`
Signature publique :
```text
HttpTransportPool::get_block_height(role, Option<&SolanaContextConfig>)
-> Result<u64>
```
La configuration réutilise le contrat partagé existant :
```text
commitment
minContextSlot
```
Un `None` ou un `SolanaContextConfig::default()` produit une liste de paramètres vide :
```json
[]
```
Une configuration non vide conserve exactement les champs fournis, par exemple :
```json
[
{
"commitment": "finalized",
"minContextSlot": 429000000
}
]
```
Aucune valeur de commitment n'est inventée localement lorsque la configuration est absente.
### `getBlockTime`
Signature publique :
```text
HttpTransportPool::get_block_time(role, slot)
-> Result<Option<i64>>
```
Le wrapper conserve sans perte les deux états de résultat :
```text
JSON i64 -> Some(timestamp)
JSON null -> None
```
Transport ne convertit pas le timestamp en date/heure applicative et ne synthétise aucune valeur pour `null`.
### `getFirstAvailableBlock`
Signature publique :
```text
HttpTransportPool::get_first_available_block(role)
-> Result<u64>
```
Le wrapper envoie exactement :
```json
[]
```
et retourne le slot `u64` fourni par le runtime.
### `minimumLedgerSlot`
Signature publique :
```text
HttpTransportPool::minimum_ledger_slot(role)
-> Result<u64>
```
Le wrapper envoie exactement :
```json
[]
```
et retourne le slot minimum `u64` fourni par le nœud, sans lui appliquer de sémantique de rétention locale.
## Discipline `#[cfg(test)]` héritée de `pre.002-fix.001`
La correction précédente est prolongée explicitement :
```text
runtime en pre.003
SolanaBlockCommitment::decode_wire
WireBlockCommitment
reste test-only
SolanaGetBlockConfig::{is_empty,to_json_value}
SolanaBlockProductionRange::to_json_value
SolanaBlockProductionConfig::{is_empty,to_json_value}
SolanaBlockProduction::decode_wire
SolanaBlockReward::decode_wire
SolanaBlockTransaction::decode_wire
SolanaConfirmedBlock::decode_wire
SolanaPerformanceSample::decode_wire
helpers privés transaction/reward de bloc
autres WireBlock*
helpers/wires Economics
```
Aucun `#[allow(dead_code)]` n'est introduit.
## Fixtures HTTP ajoutées
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_block_commitment.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_height.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_time.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_time.null.json
crates/ksp-onchain-transport-lib/fixtures/http/get_first_available_block.success.json
crates/ksp-onchain-transport-lib/fixtures/http/minimum_ledger_slot.success.json
```
Les fixtures sont des enveloppes JSON-RPC complètes et déterministes utilisées par les tests HTTP locaux.
## Tests
`unit_tests/rpc_blocks.rs` ajoute six tests async de wrapper :
```text
typed_get_block_commitment_serializes_slot_and_preserves_distribution
typed_get_block_height_serializes_context_config
typed_get_block_height_omits_explicitly_empty_config
typed_get_block_time_preserves_timestamp_and_null
typed_get_first_available_block_has_no_params
typed_minimum_ledger_slot_has_no_params
```
Ils vérifient les requêtes HTTP réellement émises vers le serveur fixture local, et pas seulement des helpers de sérialisation isolés.
`tests/public_api.rs` ajoute une canarie qui référence les cinq méthodes directement depuis `HttpTransportPool` afin de verrouiller leur disponibilité publique.
`tests/release_completeness.rs` :
- renomme la canarie globale `V0_2_4` qui parlait encore de l'état « before wrappers » ;
- ajoute une canarie `pre.003` qui verrouille exactement les cinq méthodes de cette tranche et leur classification `Read / RetrySafe`.
Après application, les compteurs attendus pour la crate Transport sont donc au minimum :
```text
unit tests 196 -> 202
public API tests 20 -> 21
release-completeness 15 -> 16
smoke Devnet 1 ignoré comme prévu
```
Ces nombres sont des attentes dérivées des tests ajoutés ; les validations Cargo doivent être exécutées sur le checkout de développement.
## Documentation de crate
La rustdoc racine de `ksp-onchain-transport-lib` est mise à jour pour refléter l'état réel de `0.2.4-pre.003` : les primitives Blocks/Economics sont acquises et cinq wrappers Blocks sont désormais actifs.
Le plan `011`, le ROADMAP et le CHANGELOG restent inchangés : aucun redécoupage de release ni jalon documentaire final n'est nécessaire dans cette tranche.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
```
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_block_commitment.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_height.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_time.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_time.null.json
crates/ksp-onchain-transport-lib/fixtures/http/get_first_available_block.success.json
crates/ksp-onchain-transport-lib/fixtures/http/minimum_ledger_slot.success.json
deltas/0.2.4/pre.003.md
```
## Fichiers supprimés
Aucun.
## Fichiers volontairement inchangés
```text
crates/ksp-onchain-transport-lib/src/rpc_method.rs
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
ROADMAP.md
CHANGELOG.md
deltas/0.2.4/pre.001.md
deltas/0.2.4/pre.001-fix.001.md
deltas/0.2.4/pre.002.md
deltas/0.2.4/pre.002-fix.001.md
```
Aucun delta historique n'est réécrit.
## Validations exécutées dans le sandbox
Le sandbox ne fournit pas le toolchain Rust. Les contrôles effectués sont donc statiques :
- parsing des six nouvelles fixtures JSON ;
- contrôle du signal Cargo `0.2.4-pre.3` ;
- contrôle de présence exacte des cinq nouvelles méthodes publiques ;
- contrôle que seul le wire `getBlockCommitment` nécessaire à cette tranche sort de `#[cfg(test)]` ;
- contrôle qu'aucun `#[allow(dead_code)]` n'est introduit ;
- scan du nouveau code de production pour `unwrap`, `expect`, `panic!` et `unsafe` ;
- contrôle différentiel contre `0.2.4-pre.002-fix.001` ;
- contrôle des versions d'en-tête des fichiers modifiés.
## Validations Cargo à exécuter
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Le critère attendu est : aucune régression, aucun warning introduit par `pre.003`, et les nouveaux compteurs `202 / 21 / 16` si aucune autre modification locale n'ajoute de test.
Le commit attendu après validation est :
```text
v0.2.4-pre.003
```
## Questions ouvertes
Aucune pour cette tranche.
## Suite
`0.2.4-pre.004` reste dédiée aux ranges/performance :
```text
getBlocks
getBlocksWithLimit
getRecentPerformanceSamples
```
avec les validations déterministes déjà cadrées dans le plan : plage maximale de 500 000 slots, `limit <= 500000`, `limit <= 720` pour les performance samples, et conservation des formes de réponse sans perte.

423
deltas/0.2.4/pre.004.md Normal file
View File

@@ -0,0 +1,423 @@
<!-- file: deltas/0.2.4/pre.004.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.004` — ranges Blocks et performance samples
## Base requise
Livraison précédente :
```text
0.2.4-pre.003
workspace.package.version = "0.2.4-pre.3"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 202 unit tests OK
21 public API tests OK
16 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Le plan canonique `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` passe de la version 2 à la version 3 dans cette livraison afin de corriger explicitement la config runtime stable de `getBlocks` / `getBlocksWithLimit` : Agave `v4.2.1` utilise `RpcContextConfig` avec `commitment` et `minContextSlot`.
## Objectif
Implémenter exactement la tranche `pre.004` prévue :
```text
getBlocks
getBlocksWithLimit
getRecentPerformanceSamples
```
Les trois méthodes sont déjà enregistrées dans la partition `V0_2_4 / Blocks` et classées `Read / RetrySafe`. Cette prerelease ne modifie donc pas le registre central.
Après cette tranche, huit des dix wrappers Blocks de `0.2.4` sont matérialisés :
```text
pre.003
getBlockCommitment
getBlockHeight
getBlockTime
getFirstAvailableBlock
minimumLedgerSlot
pre.004
getBlocks
getBlocksWithLimit
getRecentPerformanceSamples
```
Restent volontairement différés :
```text
pre.005 getBlockProduction
pre.006 getBlock
```
Les cinq méthodes Economics restent également hors scope.
## Réaudit RPC de la tranche
Le contrat HTTP courant a été revérifié avant implémentation et reste conforme au plan `pre.001` :
- `getBlocks` accepte `start_slot`, un second paramètre qui peut être soit `end_slot`, soit un `RpcContextConfig`, puis éventuellement ce même type de config en troisième position ; la config stable contient `commitment` et `minContextSlot`, et la plage maximale est de `500_000` slots ;
- `getBlocksWithLimit` accepte `start_slot`, `limit` puis un `RpcContextConfig` optionnel `{commitment,minContextSlot}` ; `limit` ne doit pas dépasser `500_000` ;
- `getRecentPerformanceSamples` accepte un `limit` optionnel avec maximum `720`; l'absence du paramètre laisse le runtime appliquer son défaut ;
- les résultats `getBlocks` / `getBlocksWithLimit` restent des listes ordonnées de slots `u64` ;
- `getRecentPerformanceSamples` reste une liste dans l'ordre fourni par le serveur et expose `slot`, `numTransactions`, `numSlots`, `samplePeriodSecs`, avec `numNonVoteTransactions` conservé comme champ optionnel pour compatibilité avec les anciens wires.
Aucun nouvel overload ni nouvelle limite n'a été identifié. En revanche, le cross-audit source Agave a clarifié un champ stable que la page publique n'affiche pas : `minContextSlot` fait partie du `RpcContextConfig` accepté par `getBlocks` et `getBlocksWithLimit`. Le plan vivant est corrigé en conséquence sous sa version 3.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.3 -> 0.2.4-pre.4
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## `getBlocks`
Signature publique :
```text
HttpTransportPool::get_blocks(
role,
start_slot,
Option<end_slot>,
Option<&SolanaContextConfig>,
) -> Result<Vec<u64>>
```
Le wrapper préserve les quatre formes retenues par `KSP-TRANSPORT-007` :
```json
[430000100]
[430000100, 430000109]
[430000100, {"commitment":"confirmed", "minContextSlot":429999999}]
[430000100, 430000109, {"commitment":"confirmed", "minContextSlot":429999999}]
```
Un `Some(SolanaContextConfig::default())` reste un overload config explicite :
```json
[430000100, {}]
```
Il n'est pas réécrit silencieusement en `[430000100]`.
Validation déterministe avant I/O :
```text
commitment = processed -> rejet
end >= start et end - start > 500_000 -> rejet
```
La borne est inclusive :
```text
end - start = 500_000 -> valide
```
Le cas :
```text
end < start
```
reste valide et est envoyé au runtime ; KSP accepte alors le tableau vide retourné sans fabriquer une erreur locale ni synthétiser une autre réponse.
Le résultat `Vec<u64>` est conservé dans l'ordre exact retourné par le serveur ; aucun tri ni remplissage des slots absents n'est effectué.
## `getBlocksWithLimit`
Signature publique :
```text
HttpTransportPool::get_blocks_with_limit(
role,
start_slot,
limit,
Option<&SolanaContextConfig>,
) -> Result<Vec<u64>>
```
Validation déterministe avant I/O :
```text
commitment = processed -> rejet
limit > 500_000 -> rejet
```
Les deux bornes importantes sont couvertes explicitement :
```text
limit = 0 -> valide
limit = 500_000 -> valide
limit = 500_001 -> rejet avant I/O
```
Aucune borne minimale artificielle `1` n'est ajoutée.
Comme pour `getBlocks`, l'ordre et les éventuels trous de slots de la réponse sont laissés au runtime et préservés par Transport.
## `getRecentPerformanceSamples`
Signature publique :
```text
HttpTransportPool::get_recent_performance_samples(
role,
Option<limit>,
) -> Result<Vec<SolanaPerformanceSample>>
```
Wire sans limite explicite :
```json
[]
```
Wire avec maximum explicite :
```json
[720]
```
Validation déterministe :
```text
limit <= 720 -> valide
limit > 720 -> rejet avant I/O
```
Le wrapper n'introduit aucune borne minimale locale et ne remplace pas l'absence de limite par une valeur synthétique `720` dans la requête.
`SolanaPerformanceSample`, introduit en `pre.002`, est désormais consommé par un wrapper runtime. Son décodeur interne et `WirePerformanceSample` sortent donc de `#[cfg(test)]`.
Le décodage conserve :
```text
slot
numTransactions
numNonVoteTransactions présent ou omis
numSlots
samplePeriodSecs
```
L'ordre retourné par le serveur est conservé sans tri local.
## Discipline `#[cfg(test)]`
La règle introduite par `pre.002-fix.001` reste appliquée strictement.
Nouveaux éléments devenus runtime en `pre.004` :
```text
SolanaPerformanceSample::decode_wire
WirePerformanceSample
```
Éléments déjà runtime en `pre.003` :
```text
SolanaBlockCommitment::decode_wire
WireBlockCommitment
```
Restent test-only jusqu'à leur tranche :
```text
SolanaGetBlockConfig::{is_empty,to_json_value}
SolanaBlockProductionRange::to_json_value
SolanaBlockProductionConfig::{is_empty,to_json_value}
SolanaBlockProduction::decode_wire
SolanaBlockReward::decode_wire
SolanaBlockTransaction::decode_wire
SolanaConfirmedBlock::decode_wire
helpers privés transaction/reward de bloc
WireBlockProduction*
WireBlockReward
WireBlockTransaction
WireConfirmedBlock
helpers/wires Economics
```
Aucun `#[allow(dead_code)]` n'est introduit.
## Fixtures HTTP ajoutées
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.empty.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.empty.json
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_performance_samples.success.json
```
Les réponses de blocs utilisent volontairement des slots non continus afin de prouver que Transport ne synthétise pas les slots absents.
La fixture performance contient un échantillon courant avec `numNonVoteTransactions` et un échantillon compatible ancien wire sans ce champ.
## Tests unitaires ajoutés
`unit_tests/rpc_blocks.rs` ajoute six tests async :
```text
typed_get_blocks_covers_all_four_overloads_and_preserves_server_order
typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversized_before_io
typed_block_range_wrappers_reject_processed_commitment_before_io
typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_maximum
typed_get_recent_performance_samples_preserves_default_explicit_limit_order_and_older_shape
typed_get_recent_performance_samples_rejects_above_720_before_io
```
Ils couvrent notamment :
```text
getBlocks [start]
getBlocks [start,end]
getBlocks [start,config]
getBlocks [start,end,config]
getBlocks [start,{}]
getBlocks end < start
getBlocks différence = 500_000
autorejet getBlocks différence = 500_001
commitment processed rejeté avant I/O
getBlocksWithLimit 0
getBlocksWithLimit 500_000
getBlocksWithLimit 500_001 rejeté avant I/O
getRecentPerformanceSamples sans limite
getRecentPerformanceSamples 720
getRecentPerformanceSamples 721 rejeté avant I/O
ordre des résultats conservé
ancien sample sans numNonVoteTransactions accepté
```
## Canaries d'intégration
`tests/public_api.rs` ajoute :
```text
public_v0_2_4_pre_004_range_and_performance_wrappers_are_available_from_crate_root
```
La canarie référence directement :
```text
HttpTransportPool::get_blocks
HttpTransportPool::get_blocks_with_limit
HttpTransportPool::get_recent_performance_samples
```
`tests/release_completeness.rs` ajoute :
```text
release_v0_2_4_pre_004_range_performance_subset_is_exact_and_retry_safe
```
Elle verrouille le sous-ensemble exact :
```text
getBlocks
getBlocksWithLimit
getRecentPerformanceSamples
```
et leur classification commune :
```text
HttpRpcCoverageRelease::V0_2_4
HttpRpcCategory::Blocks
RpcOperationKind::Read
TransportRetryClass::RetrySafe
```
## Frontières architecturales
Cette tranche ne modifie pas les frontières établies :
```text
Transport -X-> Config
Transport -X-> Store
Transport -X-> Program
Transport -X-> tracing direct
```
Les wrappers passent exclusivement par `execute_standard_rpc` et la politique centrale de retry.
Aucun client RPC Solana haut niveau, aucun retry local, aucune lecture d'environnement/configuration et aucune dépendance externe nouvelle ne sont introduits.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
```
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.empty.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.empty.json
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_performance_samples.success.json
deltas/0.2.4/pre.004.md
```
Aucun fichier n'est supprimé.
## Validation à exécuter après application
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Compteurs attendus si aucun test existant n'est ajouté/supprimé localement entre-temps :
```text
208 unit tests
22 public API tests
17 release-completeness tests
1 smoke Devnet ignoré comme prévu
0 échec
```
Le sandbox de génération ne possède pas le toolchain Rust ; aucune compilation locale n'est revendiquée dans ce delta.
## Suite
La tranche suivante reste celle prévue par le plan :
```text
0.2.4-pre.005
getBlockProduction
```
Elle devra activer uniquement les helpers runtime nécessaires à `SolanaBlockProductionConfig`, `SolanaBlockProductionRange` et `SolanaBlockProduction`, puis couvrir `identity`, `range`, le résultat contextualisé et la validation `lastSlot >= firstSlot`.
## Commit attendu
Après validation locale réussie :
```text
v0.2.4-pre.004
```

355
deltas/0.2.4/pre.005.md Normal file
View File

@@ -0,0 +1,355 @@
<!-- file: deltas/0.2.4/pre.005.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.005` — `getBlockProduction`
## Base requise
Livraison précédente :
```text
0.2.4-pre.004
workspace.package.version = "0.2.4-pre.4"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 208 unit tests OK
22 public API tests OK
17 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
## Objectif
Implémenter exactement la tranche dédiée prévue par le plan `0.2.4` :
```text
getBlockProduction
```
Cette méthode est déjà enregistrée dans la partition :
```text
V0_2_4 / Blocks / Read / RetrySafe
```
Le registre central n'est donc pas modifié.
Après cette tranche, neuf des dix wrappers Blocks de `0.2.4` sont matérialisés. Il reste volontairement :
```text
pre.006 getBlock
```
Les cinq méthodes Economics restent hors scope jusqu'aux tranches `pre.007` et `pre.008`.
## Réaudit RPC de la tranche
Le contrat stable a été recroisé avec Agave `v4.2.1` avant implémentation.
`RpcBlockProductionConfig` expose exactement :
```text
identity: Option<String>
range: Option<{firstSlot, lastSlot?}>
commitment: Option<CommitmentConfig>
```
Le résultat est contextualisé :
```text
RpcResponse<RpcBlockProduction>
```
avec :
```text
context
value.byIdentity
value.range.firstSlot
value.range.lastSlot
```
`byIdentity` associe chaque validator identity aux comptes :
```text
(leader slots, blocks produced)
```
Le runtime Agave rejette `lastSlot < firstSlot`. KSP effectue donc le même contrôle déterministe avant I/O.
Aucune restriction locale `confirmed/finalized` n'est appliquée au commitment de `getBlockProduction` : le `CommitmentConfig` général du runtime est préservé, y compris `processed`.
Aucun nouvel overload, champ ou type de résultat n'a été identifié ; le plan `011` reste donc inchangé en version 3.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.4 -> 0.2.4-pre.5
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## Wrapper typed `getBlockProduction`
Signature publique :
```text
HttpTransportPool::get_block_production(
role,
Option<&SolanaBlockProductionConfig>,
) -> Result<SolanaRpcResponse<SolanaBlockProduction>>
```
La configuration publique acquise en `pre.002` est réutilisée sans duplication :
```text
SolanaBlockProductionConfig
SolanaBlockProductionRange
```
Exemple complet :
```json
[
{
"commitment":"finalized",
"identity":"11111111111111111111111111111111",
"range":{"firstSlot":430000000,"lastSlot":430000099}
}
]
```
Une plage ouverte conserve l'absence de `lastSlot` :
```json
[
{
"commitment":"processed",
"range":{"firstSlot":430000000}
}
]
```
Un `Some(SolanaBlockProductionConfig::default())` est canoniquement omis et produit :
```json
[]
```
Il n'existe ici aucun overload positional dont la distinction dépendrait d'un objet vide ; l'omission est donc équivalente au config runtime par défaut.
## Validation déterministe avant I/O
Lorsque les deux bornes sont explicites :
```text
lastSlot >= firstSlot -> valide
lastSlot < firstSlot -> rejet avant I/O
```
L'erreur KSP utilise le domaine partagé :
```text
ERROR_CODE_INVALID_RPC_PARAMETERS
```
avec contexte non secret :
```text
rpc_method
first_slot
last_slot
```
Aucune autre validation économique ou de commitment n'est inventée.
## Résultat contextualisé
Le wrapper préserve le `SolanaRpcResponse<T>` commun :
```text
context.slot
context.apiVersion?
value.byIdentity
value.range
```
Les clés de `byIdentity` sont décodées en `Pubkey` KSP. Une clé wire invalide ferme le décodage avec `ERROR_CODE_INVALID_RESPONSE` sans recopier la valeur fautive dans le diagnostic.
Les couples :
```text
(leader slots, blocks produced)
```
sont conservés exactement ; Transport ne recalcule pas les statistiques et n'interprète pas leur économie.
## Discipline `#[cfg(test)]`
La règle introduite par `pre.002-fix.001` reste appliquée.
Éléments devenus runtime dans cette tranche parce qu'ils ont désormais un consommateur réel :
```text
SolanaBlockProductionRange::to_json_value
SolanaBlockProductionConfig::is_empty
SolanaBlockProductionConfig::to_json_value
SolanaBlockProduction::decode_wire
WireBlockProductionRange
WireBlockProduction
WireBlockProductionRpcResponse
```
Éléments déjà runtime des tranches précédentes :
```text
SolanaBlockCommitment::decode_wire
WireBlockCommitment
SolanaPerformanceSample::decode_wire
WirePerformanceSample
```
Restent test-only jusqu'à `pre.006` :
```text
SolanaGetBlockConfig::{is_empty,to_json_value}
SolanaBlockReward::decode_wire
SolanaBlockTransaction::decode_wire
SolanaConfirmedBlock::decode_wire
helpers privés transaction/reward de bloc
WireBlockReward
WireBlockTransaction
WireConfirmedBlock
```
Tous les helpers/wires Economics restent également test-only jusqu'à leurs prereleases dédiées.
Aucun `#[allow(dead_code)]` n'est introduit.
## Fixtures HTTP ajoutées
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.invalid_identity.json
```
Elles couvrent :
- contexte `slot + apiVersion` ;
- `byIdentity` typé ;
- plage effective ;
- erreur fermée sur identity wire invalide.
## Tests ajoutés
Quatre unit tests HTTP sont ajoutés :
```text
typed_get_block_production_serializes_full_config_and_decodes_contextual_result
typed_get_block_production_omits_empty_config_and_supports_open_range_with_processed_commitment
typed_get_block_production_rejects_reversed_range_before_io
typed_get_block_production_rejects_invalid_wire_identity_without_echoing_value
```
Une canarie public API vérifie que le wrapper reste disponible depuis la racine de crate :
```text
public_v0_2_4_pre_005_block_production_wrapper_is_available_from_crate_root
```
Une canarie release-completeness vérifie le descriptor :
```text
release_v0_2_4_pre_005_block_production_subset_is_exact_and_retry_safe
```
Compteurs attendus après validation locale :
```text
212 unit tests
23 public API tests
18 release-completeness tests
1 smoke Devnet ignoré
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
```
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.invalid_identity.json
deltas/0.2.4/pre.005.md
```
Aucun fichier n'est supprimé.
## Validation attendue
À exécuter après application de l'overlay :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Les critères de fermeture sont :
```text
aucun warning introduit
212 unit tests OK
23 public API tests OK
18 release-completeness tests OK
smoke Devnet ignoré comme prévu
0 échec
```
Le sandbox de préparation ne possède pas le toolchain Cargo/Rust ; aucune compilation locale n'est revendiquée par ce delta.
## Commit attendu
Après validation locale :
```text
v0.2.4-pre.005
```
## Suite
La prerelease suivante reste :
```text
0.2.4-pre.006
```
Scope prévu :
```text
réaudit SIMD-0298 / SIMD-0307
getBlock moderne
bare encoding legacy
transactionDetails
wire bloc riche
SIMD-0118 numRewardPartitions
SIMD-0291 commissionBps
canary version transaction numérique non zéro / SIMD-0385
```

541
deltas/0.2.4/pre.006.md Normal file
View File

@@ -0,0 +1,541 @@
<!-- file: deltas/0.2.4/pre.006.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.006` — `getBlock` complet
## Base requise
Livraison précédente :
```text
0.2.4-pre.005
workspace.package.version = "0.2.4-pre.5"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 212 unit tests OK
23 public API tests OK
18 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
## Objectif
Achever la famille Blocks de `0.2.4` avec le wrapper le plus riche de la release :
```text
getBlock
```
La méthode est déjà enregistrée :
```text
V0_2_4 / Blocks / Read / RetrySafe / StableWithDeprecatedLegacy
```
Le registre central n'est pas modifié.
Après cette tranche, les dix wrappers Blocks de `0.2.4` sont matérialisés. Les cinq wrappers Economics restent volontairement pour `pre.007` et `pre.008`.
## Réaudit primaire `getBlock` — Agave `v4.2.1`
La source runtime stable confirme que `getBlock` reçoit :
```text
slot
Option<RpcEncodingConfigWrapper<RpcBlockConfig>>
```
La forme wrapper conserve donc deux familles de requêtes :
```text
moderne : [slot, { ...RpcBlockConfig }]
legacy : [slot, "encoding"]
```
La forme legacy bare encoding reste supportée par le runtime pour backwards compatibility, mais n'est pas la forme KSP recommandée.
`RpcBlockConfig` expose exactement dans la baseline stable :
```text
encoding: Option<UiTransactionEncoding>
transactionDetails: Option<TransactionDetails>
rewards: Option<bool>
commitment: Option<CommitmentConfig>
maxSupportedTransactionVersion: Option<u8>
```
Aucun champ `footer` n'existe dans `RpcBlockConfig` stable `v4.2.1`.
Défauts appliqués côté runtime lorsqu'ils ne sont pas fournis :
```text
encoding = json
transactionDetails = full
rewards = true
```
Le commitment explicitement fourni doit être au moins `confirmed`; KSP rejette donc `processed` avant I/O avec le domaine d'erreur partagé.
## Réaudit SIMD footer / transaction version
### SIMD-0307 — Add Block Footer
Le document est toujours en `Review` et propose :
```text
config getBlock : footer
réponse : footer fields, dont blockProducerTimeNanos / blockUserAgent
```
Ces champs sont absents de la baseline Agave `v4.2.1`; ils ne sont pas inventés dans KSP.
### SIMD-0298 — Add `bank_hash` to block footer
Le document SIMD reste au statut `Idea`. Il propose un futur `bank_hash` dans le block footer. Ce champ n'existe pas dans le wire RPC stable audité et n'est pas ajouté spéculativement.
Le tracker amont lié à Bankless Leader/Alpenglow rend néanmoins ce sujet important pour le réaudit final : l'existence d'une implémentation future du mécanisme ne transforme pas sa forme RPC en contrat stable `v4.2.1`.
### SIMD-0301 — `parent_bank_hash`
Le PR SIMD-0301 proposait de remplacer `bank_hash` par `parent_bank_hash` et de supersede SIMD-0298. Il a été fermé le 28 janvier 2026 sans merge. Il ne constitue donc ni un SIMD adopté ni un wire stable.
Le plan vivant ajoute `0301` à la watchlist de `pre.009` avec `0298` et `0307`, afin de ne pas figer prématurément le nom ou même la présence d'un futur hash de footer.
### SIMD-0385 — Transaction V1
SIMD-0385 reste en `Review`, tandis que la baseline Agave `v4.2.1` possède déjà le plumbing transaction-status nécessaire aux messages V1.
KSP conserve une représentation générique :
```text
SolanaTransactionVersion::Legacy
SolanaTransactionVersion::Number(u8)
```
Le wrapper `getBlock` ajoute une canary avec :
```text
version = 1
```
Il ne régresse donc pas vers un modèle limité à `legacy` ou `v0`.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.5 -> 0.2.4-pre.6
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## Forme moderne
Signature publique :
```text
HttpTransportPool::get_block(
role,
slot,
Option<&SolanaGetBlockConfig>,
) -> Result<Option<SolanaConfirmedBlock>>
```
Les trois états de la config restent distingués lorsque leur forme wire est utile :
```text
None -> [slot]
Some(default config) -> [slot, {}]
Some(config) -> [slot, {...}]
```
La config moderne acquise en `pre.002` est réutilisée, sans DTO parallèle.
## Forme legacy retained
La surface publique dédiée est :
```text
#[deprecated]
HttpTransportPool::get_block_legacy(role, slot, encoding)
```
Elle émet le warning KSP centralisé et encode exactement :
```text
[slot, "binary"]
[slot, "base58"]
[slot, "base64"]
[slot, "json"]
[slot, "jsonParsed"]
```
Les cinq labels de `UiTransactionEncoding` stable sont couverts, y compris `binary`, lui-même legacy mais encore retenu par Agave pour compatibilité.
## `transactionDetails` complet
Les quatre variantes stables sont couvertes :
```text
full
signatures
none
accounts
```
Leurs conséquences wire Agave sont conservées sans synthèse :
```text
full -> transactions présent, signatures omis
signatures -> transactions omis, signatures présent
none -> transactions omis, signatures omis
accounts -> transactions présent sous représentation account-list, signatures omis
```
La représentation `accounts` reste un payload transaction JSON lossless; elle n'est pas forcée dans la structure plus riche d'une transaction `full`.
## Encodings et union transaction
Les cinq encodings modernes et legacy sont testés. Le DTO partagé `SolanaEncodedTransaction` couvre les trois formes de réponse effectivement nécessaires :
```text
LegacyBinary(String) <- encoding "binary"
Binary { data, Base58 ou Base64 } <- encodings "base58" / "base64"
Json(serde_json::Value) <- encodings "json" / "jsonParsed" et transactionDetails=accounts
```
Aucun décodage binaire, Program-specific ou message-specific n'est introduit dans Transport.
## Résultat bloc riche
`getBlock` retourne :
```text
Option<SolanaConfirmedBlock>
```
Le `null` top-level du RPC est conservé comme `None`.
Pour un bloc présent, KSP conserve :
```text
previousBlockhash
blockhash
parentSlot
transactions : SolanaWireField<Vec<SolanaBlockTransaction>>
signatures : SolanaWireField<Vec<String>>
rewards : SolanaWireField<Vec<SolanaBlockReward>>
numRewardPartitions : SolanaWireField<u64>
blockTime : Option<i64>
blockHeight : Option<u64>
```
Les champs dont la présence dépend du mode de réponse gardent donc leurs états wire `omitted` / `null` / `value` au lieu d'être normalisés.
## Transactions de bloc
Un élément `transactions[]` possède son propre DTO, distinct de `SolanaConfirmedTransaction` :
```text
transaction
meta
version
```
Le DTO de `getTransaction` n'est pas réutilisé car son `slot` et son `blockTime` sont top-level et n'appartiennent pas à chaque transaction d'un bloc.
`meta` reste :
```text
SolanaWireField<serde_json::Value>
```
Cela préserve sans perte les champs transaction-status actuels et leurs extensions, notamment les rewards imbriqués, sans transférer de logique métier dans Transport.
## Rewards et SIMD-0291
Le reward de bloc conserve :
```text
pubkey: Pubkey
lamports: i64
postBalance: u64
rewardType: Option<String>
commission: Option<u8>
commissionBps: SolanaWireField<u16>
```
`commission` et `commissionBps` restent indépendants : KSP ne dérive jamais l'un depuis l'autre.
Les fixtures couvrent :
```text
commission = null + commissionBps présent
commission présent + commissionBps omis
commissionBps imbriqué dans transaction.meta
```
## Reward partitions et SIMD-0118
`numRewardPartitions` reste un champ wire propre :
```text
SolanaWireField<u64>
```
Les fixtures couvrent `value`, omission et `null` de compatibilité. Aucun `0` n'est synthétisé.
Point important confirmé dans l'encodeur Agave : `numRewardPartitions` est renseigné depuis le bloc indépendamment de `show_rewards`. Avec :
```text
rewards: false
```
KSP accepte donc correctement :
```text
rewards omis
numRewardPartitions présent
```
sans lier artificiellement les deux champs.
## Rewards désactivés dans `meta`
Le runtime Agave n'utilise pas exactement la même sérialisation interne selon les chemins `full/jsonParsed`, `accounts`, etc. pour masquer les rewards de metadata.
Transport ne normalise pas ces sous-arbres. La fixture `full` avec `rewards:false` conserve explicitement un `meta.rewards = null`, tandis que les formes qui omettent ce champ restent également préservées grâce au `serde_json::Value` lossless.
## Validation déterministe avant I/O
La seule validation locale ajoutée à `getBlock` est :
```text
commitment = processed -> ERROR_CODE_INVALID_RPC_PARAMETERS avant I/O
commitment = confirmed -> autorisé
commitment = finalized -> autorisé
```
KSP ne tente pas de prévalider :
```text
disponibilité du bloc
slot cleaned/skipped
historique local ou long-term storage
support réel d'une version de transaction par le caller/runtime
```
Ces états appartiennent au runtime RPC.
## Erreurs RPC runtime préservées
Deux erreurs Agave caractéristiques sont couvertes par fixtures :
```text
-32004 BlockNotAvailable
-32015 UnsupportedTransactionVersion
```
Toutes deux restent des `ERROR_CODE_RPC_APPLICATION_ERROR` dans la façade KSP; elles ne sont ni transformées en erreurs de validation locale ni avalées en `None`.
Le `null` success result reste distinct d'une erreur RPC.
## Discipline `#[cfg(test)]`
La règle introduite par `pre.002-fix.001` reste appliquée.
Deviennent runtime dans cette tranche parce que `getBlock` les consomme réellement :
```text
SolanaGetBlockConfig::to_json_value
SolanaBlockReward::decode_wire
SolanaBlockTransaction::decode_wire
SolanaConfirmedBlock::decode_wire
decode_block_transaction_version
decode_block_transactions
decode_block_rewards
WireBlockReward
WireBlockTransaction
WireConfirmedBlock
```
`SolanaGetBlockConfig::is_empty` reste test-only : le wrapper doit justement préserver un `Some(default config)` explicite comme `{}` et n'a aucun besoin runtime de le canoniser.
Les helpers Economics restent test-only jusqu'à `pre.007`/`pre.008`.
Aucun `#[allow(dead_code)]` n'est introduit.
## Fixtures HTTP ajoutées
```text
get_block.full.success.json
get_block.full_no_rewards.success.json
get_block.signatures.success.json
get_block.none.success.json
get_block.accounts.success.json
get_block.binary.success.json
get_block.base58.success.json
get_block.base64.success.json
get_block.json.success.json
get_block.json_parsed.success.json
get_block.null.json
get_block.omitted_fields.success.json
get_block.null_fields.success.json
get_block.error_block_not_available.json
get_block.error_unsupported_version.json
```
Elles couvrent notamment :
- rich full wire ;
- cinq encodings ;
- quatre `transactionDetails` ;
- legacy bare encoding ;
- result `null` ;
- états omitted/null/value des champs top-level conditionnels ;
- `rewards:false` sans disparition de `numRewardPartitions` ;
- SIMD-0118 ;
- SIMD-0291 ;
- transaction version numérique `1` pour la compatibilité préparée SIMD-0385 ;
- erreurs RPC bloc/version.
## Tests ajoutés
Neuf unit tests HTTP sont ajoutés :
```text
typed_get_block_modern_full_config_preserves_rich_wire_and_simd_fields
typed_get_block_preserves_absent_and_explicit_empty_modern_config
typed_get_block_legacy_covers_all_bare_encoding_labels_and_response_shapes
typed_get_block_modern_covers_all_agave_v4_2_1_encoding_labels
typed_get_block_covers_full_signatures_none_and_accounts_transaction_details
typed_get_block_preserves_null_and_omitted_top_level_wire_states
typed_get_block_preserves_unsupported_version_rpc_error
typed_get_block_preserves_block_not_available_rpc_error
typed_get_block_rejects_processed_commitment_before_io
```
Une canary public API est ajoutée pour les deux formes publiques moderne/legacy et les getters de config.
Une canary release-completeness vérifie désormais que la famille Blocks `V0_2_4` possède exactement ses dix descriptors `Read / RetrySafe` et que `getBlock` conserve son marqueur `StableWithDeprecatedLegacy`.
Compteurs attendus après validation :
```text
221 unit tests
24 public API tests
19 release-completeness tests
1 smoke Devnet ignoré
```
## Documentation vivante
`docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` passe en version 4.
Modifications :
- réaudit `pre.006` de SIMD-0298/0307 ;
- ajout de SIMD-0301 à la watchlist finale après constat de fermeture sans merge ;
- ajout des sources Agave transaction-status/custom-error effectivement utilisées pour vérifier le wire riche et les erreurs ;
- `pre.009` réauditera ensemble au minimum `0298/0301/0307` pour le futur footer.
Les deux tableaux du plan conservent leur structure source alignée :
```text
19 lignes au total
4 colonnes
5 caractères `|` par ligne
largeurs 31 / 152 / 163 / 302
```
Aucun ancien delta n'est modifié.
## Frontières architecturales
Cette tranche ne change aucune frontière :
```text
Transport -X-> Config
Transport -X-> Store
Transport -X-> Program
Transport -X-> tracing direct
```
Le wrapper passe uniquement par :
```text
getBlock typed
-> descriptor central
-> execute_standard_rpc
-> admission/pool/executor HTTP commun
-> parser JSON-RPC central
-> decode typed/lossless
```
Aucun client RPC Solana haut niveau, reqwest parallèle, retry local ou décodage Program n'est ajouté.
## Validation attendue
Après application :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Attendus :
```text
0 warning introduit
221 unit tests OK
24 public API tests OK
19 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Le sandbox d'échange ne possède pas de toolchain Rust ; ces validations compilées doivent donc être confirmées localement.
## Fichiers touchés
Modifiés :
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
```
Ajoutés :
```text
15 fixtures get_block*.json
deltas/0.2.4/pre.006.md
```
## Identifiant de commit attendu
Après validation locale :
```text
v0.2.4-pre.006
```
La tranche suivante reste :
```text
0.2.4-pre.007 — réaudit SIMD-0490/0550 + Economics simples
```

475
deltas/0.2.4/pre.007.md Normal file
View File

@@ -0,0 +1,475 @@
<!-- file: deltas/0.2.4/pre.007.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.007` — Economics simples + réaudit SIMD-0490/0550
## Base requise
Livraison précédente :
```text
0.2.4-pre.006
workspace.package.version = "0.2.4-pre.6"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 221 unit tests OK
24 public API tests OK
19 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
## Objectif
Matérialiser les quatre wrappers Economics simples prévus par le plan `0.2.4` :
```text
getInflationGovernor
getInflationRate
getStakeMinimumDelegation
getSupply
```
`getInflationReward` reste volontairement réservé à `pre.008`.
Les cinq descriptors Economics sont déjà enregistrés sous :
```text
V0_2_4 / Economics / Read / RetrySafe
```
Le registre central n'est pas modifié.
## Réaudit primaire SIMD-0490 — Upgrade BPF Stake Program to v5.0.0
Le document courant reste au statut :
```text
Review
```
Il prévoit notamment :
```text
minimum delegation : 1 lamport -> 1 SOL
validator support : exposer le nouveau minimum via le RPC de minimum delegation
```
Ce statut ne conduit à aucune constante KSP.
Le cross-audit Agave stable `v4.2.1` est plus important pour Transport : le runtime `getStakeMinimumDelegation` appelle déjà :
```text
stake_utils::get_minimum_delegation(
bank.feature_set.snapshot().upgrade_bpf_stake_program_to_v5
)
```
La valeur RPC est donc déjà sélectionnée par l'état runtime du feature set.
Conséquence KSP :
```text
aucun minimum 1 lamport codé en dur
aucun minimum 1 SOL codé en dur
aucune validation locale de la valeur
retour exact du u64 fourni par le runtime
```
La fixture HTTP utilise volontairement une valeur arbitraire `42_424_242` lamports pour empêcher une canary de dépendre d'une valeur économique supposée.
## Réaudit primaire SIMD-0550 — Double Disinflation Rate
Le document courant reste au statut :
```text
Review
```
Il précise explicitement que :
```text
getInflationRate
getInflationGovernor
```
reflètent la nouvelle schedule lorsqu'elle est active, mais qu'il n'y a :
```text
aucun changement d'API
```
Conséquence KSP :
- `SolanaInflationGovernor` reste un DTO de valeurs runtime `f64`;
- `SolanaInflationRate` reste un DTO de valeurs runtime `f64` + epoch;
- Transport ne réimplémente aucune formule d'inflation;
- Transport ne code en dur ni `taper = 0.15`, ni `taper = 0.30`;
- Transport ne recalcule pas `initial`;
- les fixtures utilisent des valeurs non canoniques/arbitraires afin de détecter une future normalisation locale indésirable.
Le plan `011` décrivait déjà cette politique correctement et reste byte-identique en version 4.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.6 -> 0.2.4-pre.7
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## `getInflationGovernor`
Surface publique :
```text
HttpTransportPool::get_inflation_governor(
role,
Option<&SolanaCommitmentConfig>,
) -> Result<SolanaInflationGovernor>
```
Formes wire retenues :
```text
None / config vide -> params: []
config commitment -> params: [{"commitment":"..."}]
```
Le runtime accepte son `CommitmentConfig` général. KSP n'invente donc aucune restriction `confirmed/finalized`; une canary envoie explicitement :
```text
commitment = processed
```
La réponse est décodée dans :
```text
initial
terminal
taper
foundation
foundationTerm
```
sans post-traitement économique.
## `getInflationRate`
Surface publique :
```text
HttpTransportPool::get_inflation_rate(
role,
) -> Result<SolanaInflationRate>
```
La requête est exactement :
```text
params: []
```
La réponse conserve :
```text
total
validator
foundation
epoch
```
sans recalcul local.
## `getStakeMinimumDelegation`
Surface publique :
```text
HttpTransportPool::get_stake_minimum_delegation(
role,
Option<&SolanaContextConfig>,
) -> Result<SolanaRpcResponse<u64>>
```
Les options stables sont réutilisées depuis le contrat commun :
```text
commitment
minContextSlot
```
`None` et une config explicitement vide sont canoniquement omis.
Une config complète avec :
```text
commitment = processed
minContextSlot = 431000000
```
est couverte et la réponse contextualisée conserve :
```text
context.slot
context.apiVersion
value: u64
```
`MinContextSlotNotReached` reste une erreur RPC applicative et n'est pas convertie en erreur de décodage KSP.
## `getSupply`
Surface publique :
```text
HttpTransportPool::get_supply(
role,
Option<&SolanaSupplyConfig>,
) -> Result<SolanaRpcResponse<SolanaSupply>>
```
La config conserve :
```text
commitment
excludeNonCirculatingAccountsList
```
Le booléen KSP reste `Option<bool>` afin de distinguer dans la requête :
```text
omis
false
true
```
`None` et une config entièrement vide sont canoniquement omis car le runtime leur applique les mêmes défauts.
Une valeur `false` explicite reste envoyée comme :
```json
{"excludeNonCirculatingAccountsList":false}
```
Une valeur `true` explicite reste envoyée comme :
```json
{"excludeNonCirculatingAccountsList":true}
```
Lorsque `true`, Agave retourne volontairement :
```text
nonCirculatingAccounts = []
```
sans modifier la signification des totaux retournés. KSP préserve cette liste vide telle quelle.
Le résultat contextualisé conserve :
```text
total
circulating
nonCirculating
nonCirculatingAccounts
```
Les adresses de `nonCirculatingAccounts` sont validées/typées en `Pubkey` sans fuite de la valeur fautive dans les diagnostics.
Une `ScanError` runtime de `getSupply` reste une erreur RPC applicative.
## Discipline `#[cfg(test)]`
La politique introduite par `pre.002-fix.001` est maintenue.
Deviennent runtime uniquement parce qu'ils ont maintenant un consommateur réel :
```text
SolanaInflationGovernor::decode_wire
SolanaInflationRate::decode_wire
SolanaSupplyConfig::is_empty
SolanaSupplyConfig::to_json_value
SolanaSupply::decode_wire
WireInflationGovernor
WireInflationRate
WireSupply
WireEconomicsRpcResponse<T>
```
Restent sous `#[cfg(test)]` jusqu'à `pre.008` :
```text
SolanaInflationRewardConfig::{is_empty,to_json_value}
SolanaInflationReward::decode_wire
WireInflationReward
```
Aucun `#[allow(dead_code)]` n'est ajouté.
## Chemin d'exécution
Les quatre wrappers utilisent une seule frontière privée :
```text
economics_descriptor
-> HttpTransportPool::execute_standard_rpc
```
Le helper de descriptor exige :
```text
category = Economics
coverage_release = V0_2_4
```
Transport ne crée aucun chemin HTTP parallèle et ne dépend toujours ni de Config, ni Store, ni Program, ni d'un client RPC Solana haut niveau.
## Fixtures et tests
Nouvelles fixtures HTTP :
```text
get_inflation_governor.success.json
get_inflation_rate.success.json
get_stake_minimum_delegation.success.json
get_stake_minimum_delegation.error_min_context.json
get_supply.success.json
get_supply.excluded.success.json
get_supply.error_scan.json
```
Dix nouveaux tests unitaires couvrent :
```text
getInflationGovernor config processed + valeurs runtime
getInflationGovernor config absente/vide
getInflationRate params vides + valeurs runtime
getStakeMinimumDelegation config contextuelle + valeur arbitraire
getStakeMinimumDelegation config absente/vide
getStakeMinimumDelegation MinContextSlotNotReached
getSupply false explicite + liste complète + contexte
getSupply true explicite + liste runtime vide
getSupply config absente/vide
getSupply ScanError
```
Canary public API :
```text
public_v0_2_4_pre_007_simple_economics_wrappers_are_available_from_crate_root
```
Canary release :
```text
release_v0_2_4_pre_007_simple_economics_subset_is_exact_and_retry_safe
```
Elle fixe exactement les quatre wrappers de cette tranche sans avancer `getInflationReward`.
Compteurs attendus après validation locale :
```text
231 unit tests
25 public API tests
20 release-completeness tests
1 smoke Devnet ignoré
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
```
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_governor.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_rate.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_stake_minimum_delegation.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_stake_minimum_delegation.error_min_context.json
crates/ksp-onchain-transport-lib/fixtures/http/get_supply.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_supply.excluded.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_supply.error_scan.json
deltas/0.2.4/pre.007.md
```
## Non-objectifs
Cette tranche n'implémente pas :
```text
getInflationReward
```
Elle ne modifie pas non plus :
- les 10 wrappers Blocks déjà acquis;
- les 37 wrappers `0.2.1``0.2.3`;
- le registre des 52 current + 14 historical;
- les règles de retry;
- le smoke Devnet;
- les dépendances ou features Cargo;
- le plan `011`, déjà correct pour ce scope.
## Validation attendue
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Résultat attendu :
```text
0 warning introduit
231 unit tests OK
25 public API tests OK
20 release-completeness tests OK
1 smoke Devnet ignoré
0 échec
```
## Commit attendu
```text
v0.2.4-pre.007
```
## Suite
`0.2.4-pre.008` reste dédiée à :
```text
getInflationReward
```
avec :
```text
ordre et cardinalité des adresses
null positionnels
epoch / commitment / minContextSlot
commission nullable
commissionBps SIMD-0291
absence de limite d'adresses inventée
invariants de réponse
```
Puis `pre.009` effectuera le réaudit SIMD/inventaire et la compliance HTTP finale `52/52 + 14/14`.

View File

@@ -0,0 +1,86 @@
<!-- file: deltas/0.2.4/pre.008-fix.001.md -->
<!-- version: 1 -->
# 0.2.4-pre.008-fix.001 — conformité Clippy `implicit_return`
## Base
- Livraison corrigée : `0.2.4-pre.008`
- Version Cargo de base : `0.2.4-pre.8`
- Version Cargo après fix : `0.2.4-pre.8.fix.1`
- Le delta historique `pre.008.md` et le plan `011` ne sont pas modifiés.
## Motif du fix
La validation locale de `0.2.4-pre.008` a donné :
- `cargo fmt --all` : OK ;
- `cargo check --workspace` : OK ;
- `cargo clippy --workspace --all-targets` : échec sur une unique violation de la politique workspace `-D clippy::implicit-return` ;
- `cargo test -p ksp-onchain-transport-lib` : OK avec `240` unit tests, `26` tests public API, `21` tests release-completeness et le smoke Devnet explicitement ignoré.
La violation se trouve dans `HttpTransportPool::get_inflation_reward`, dans la closure qui transforme les `Pubkey` d'entrée en valeurs JSON :
```rust
.map(|address| serde_json::Value::String(address.to_string()))
```
Le workspace impose les retours explicites, y compris dans cette closure.
## Correction
La closure devient :
```rust
.map(|address| return serde_json::Value::String(address.to_string()))
```
Aucun comportement RPC, aucune validation, aucun DTO, aucune fixture et aucune canarie ne changent.
## Fichiers
Modifiés :
- `Cargo.toml` ;
- `crates/ksp-onchain-transport-lib/src/rpc_economics.rs`.
Ajouté :
- `deltas/0.2.4/pre.008-fix.001.md`.
## Validation statique de livraison
L'environnement de construction de l'archive ne dispose pas de `cargo`, `rustc` ou `rustfmt`. Les contrôles statiques de packaging vérifient donc :
- modification limitée à la closure concernée, aux versions de fichier/Cargo et au présent delta ;
- absence de `#[allow(dead_code)]` ajouté ;
- absence de nouveau `unsafe`, `unwrap`, `expect` ou `panic!` dans le runtime modifié ;
- archive réextraite et comparée octet par octet.
## Validation locale attendue
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Résultat attendu :
- zéro warning / erreur introduit par `pre.008` ;
- `240` unit tests OK ;
- `26` tests public API OK ;
- `21` tests release-completeness OK ;
- `1` smoke Devnet ignoré ;
- aucun échec.
## Commit attendu
```text
v0.2.4-pre.008-fix.001
```
## Suite
La prochaine prerelease fonctionnelle reste `0.2.4-pre.009`, consacrée au réaudit final SIMD/inventaire, à la compliance HTTP `52/52 + 14/14`, aux smokes/graphes, à la documentation finale et au prompt `0.2.5 — Wallet foundation`.

474
deltas/0.2.4/pre.008.md Normal file
View File

@@ -0,0 +1,474 @@
<!-- file: deltas/0.2.4/pre.008.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.008` — `getInflationReward` complet + invariants positionnels
## Base requise
Livraison précédente :
```text
0.2.4-pre.007
workspace.package.version = "0.2.4-pre.7"
```
Les validations locales fournies pour cette base sont propres :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 231 unit tests OK
25 public API tests OK
20 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
## Objectif
Achever la surface Economics `0.2.4` avec le dernier wrapper prévu :
```text
getInflationReward
```
La tranche doit conserver sans perte :
```text
ordre exact des adresses d'entrée
cardinalité exacte de la réponse
null positionnels
config epoch / commitment / minContextSlot
commission nullable
commissionBps présent / omis / null de compatibilité
absence de limite métier d'adresses inventée par KSP
erreurs RPC runtime
```
À l'issue de cette tranche, les `10/10` wrappers Blocks et `5/5` wrappers Economics de `V0_2_4` sont matérialisés. La compliance globale `52/52 + 14/14` reste volontairement réservée à `pre.009`.
## Réaudit primaire du contrat Agave stable
Baseline stable conservée :
```text
Agave v4.2.1
```
Le serveur expose :
```text
getInflationReward(
address_strs: Vec<String>,
config: Option<RpcEpochConfig>,
) -> Vec<Option<RpcInflationReward>>
```
`RpcEpochConfig` contient :
```text
epoch: Option<u64>
commitment: Option<CommitmentConfig>
minContextSlot: Option<u64>
```
Le runtime reconstruit son résultat en itérant sur la liste d'adresses d'origine. Il restitue donc une position par entrée :
```text
reward trouvé -> Some(RpcInflationReward)
reward absent -> None / JSON null
```
Cette propriété justifie le contrôle KSP explicite de cardinalité : une réponse dont la longueur diffère du nombre d'adresses demandées est un wire invalide pour le wrapper typed.
## Correction de contrat découverte pendant `pre.008`
Le plan `011` n'explicitait pas encore la contrainte effective de commitment de `getInflationReward`.
Agave construit un `RpcContextConfig` depuis `RpcEpochConfig` puis appelle :
```text
getBlocksWithLimit(first_slot_in_epoch, 1, Some(context_config))
```
Cette voie applique la contrainte runtime :
```text
commitment >= confirmed
```
Conséquence KSP :
```text
commitment = processed -> rejet déterministe avant I/O
commitment = confirmed -> accepté
commitment = finalized -> accepté
commitment absent -> défaut runtime accepté
```
Le plan `011` passe donc de la version `4` à la version `5` avec :
- la nouvelle contrainte locale `getInflationReward commitment >= confirmed`;
- la matrice Economics corrigée;
- le libellé de `pre.008` complété par le réaudit de commitment.
Aucun autre changement de planning n'est effectué.
## SIMD-0291 — Commission Rate in Basis Points
Le document SIMD courant reste au statut :
```text
Review
```
La baseline Agave stable `v4.2.1` expose néanmoins déjà :
```text
RpcInflationReward {
epoch,
effective_slot,
amount,
post_balance,
commission: Option<u8>,
commission_bps: Option<u16>,
}
```
KSP conserve donc deux informations indépendantes :
```text
commission -> Option<u8>
commissionBps -> SolanaWireField<u16>
```
Aucune conversion `bps -> pourcentage` n'est réalisée. Aucun champ n'est dérivé depuis l'autre.
Le wire KSP accepte et distingue :
```text
commissionBps présent
commissionBps omis
commissionBps explicitement null pour compatibilité provider/wire
```
Même si Agave stable sérialise normalement l'absence via omission, conserver `null` séparément respecte la politique générale `SolanaWireField<T>` et évite une normalisation destructive.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.7 -> 0.2.4-pre.8
```
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
## Surface publique `getInflationReward`
Nouvelle méthode :
```text
HttpTransportPool::get_inflation_reward(
role,
&[Pubkey],
Option<&SolanaInflationRewardConfig>,
) -> Result<Vec<Option<SolanaInflationReward>>>
```
Le premier paramètre JSON-RPC est toujours la liste d'adresses, y compris lorsqu'elle est vide :
```text
[] d'adresses -> params: [[]]
```
La config optionnelle est ajoutée uniquement lorsqu'elle contient au moins un champ :
```text
None / config vide -> [addresses]
config non vide -> [addresses, config]
```
Exemple complet :
```json
[
["address-1", "address-2"],
{
"epoch": 912,
"commitment": "finalized",
"minContextSlot": 431000000
}
]
```
## Ordre, doublons et cardinalité
Le wrapper ne trie ni ne déduplique les adresses.
Une requête conceptuelle :
```text
[A, B, A]
```
reste exactement :
```text
[A, B, A]
```
et la réponse reste positionnelle :
```text
[Some(reward A), None, Some(reward A)]
```
Après décodage du tableau JSON, KSP vérifie :
```text
result.len() == addresses.len()
```
Sinon :
```text
ERROR_CODE_INVALID_RESPONSE
expected_count = ...
actual_count = ...
```
## Absence de limite d'adresses inventée
Aucune constante métier n'est ajoutée pour `getInflationReward`.
En particulier, KSP ne réutilise pas artificiellement :
```text
256 # getSignatureStatuses
1000 # getSignaturesForAddress
```
ou toute autre limite d'un RPC voisin.
Une canary HTTP construit une requête de :
```text
300 adresses
```
et vérifie qu'elle atteint réellement le serveur fixture puis que les `300` `null` sont restitués.
La limite générale de taille d'une requête HTTP reste une contrainte de transport/provider, pas une cardinalité métier inventée par ce wrapper.
## Erreurs runtime préservées
Deux erreurs spécifiques sont couvertes par fixtures :
```text
-32016 MinContextSlotNotReached
-32017 EpochRewardsPeriodActive
```
Elles restent :
```text
ERROR_CODE_RPC_APPLICATION_ERROR
```
et ne sont pas converties en erreurs de décodage locales.
Les autres erreurs runtime possibles liées à l'historique/epoch restent également transportées par le flux JSON-RPC central, notamment les familles BlockCleanedUp, BlockNotAvailable, SlotNotEpochBoundary et long-term storage lorsqu'elles surviennent réellement.
## Discipline `#[cfg(test)]`
La politique introduite par `pre.002-fix.001` reste appliquée.
Deviennent runtime uniquement parce qu'ils ont maintenant un consommateur réel :
```text
SolanaInflationRewardConfig::is_empty
SolanaInflationRewardConfig::to_json_value
SolanaInflationReward::decode_wire
WireInflationReward
```
Ils rejoignent les helpers Economics activés en `pre.007`.
Aucun `#[allow(dead_code)]` n'est ajouté.
## Chemin d'exécution
Le wrapper suit la frontière existante :
```text
get_inflation_reward
-> economics_descriptor("getInflationReward")
-> HttpTransportPool::execute_standard_rpc
-> decode_inflation_rewards
```
Le descriptor central reste :
```text
V0_2_4 / Economics / Read / RetrySafe
```
Aucun chemin HTTP parallèle n'est créé.
## Fixtures
Nouvelles fixtures HTTP :
```text
get_inflation_reward.success.json
get_inflation_reward.duplicates.json
get_inflation_reward.single_null.json
get_inflation_reward.empty.json
get_inflation_reward.many_nulls.json
get_inflation_reward.error_min_context.json
get_inflation_reward.error_epoch_rewards_active.json
```
Elles couvrent :
- config complète;
- ordre positionnel;
- doublons d'adresses;
- `null` positionnels;
- `commission` nullable;
- `commissionBps` présent / omis / null;
- tableau d'adresses vide;
- 300 adresses sans limite métier KSP;
- erreurs RPC runtime.
## Tests
Neuf nouveaux tests unitaires couvrent :
```text
config complète + positions + états commission/commissionBps
ordre des doublons + null positionnel
config absente/vide tout en conservant le paramètre addresses
liste d'adresses vide
300 adresses sans limite locale 256
processed rejeté avant I/O
mismatch de cardinalité rejeté
MinContextSlotNotReached préservé
EpochRewardsPeriodActive préservé
```
Canary public API :
```text
public_v0_2_4_pre_008_inflation_reward_wrapper_is_available_from_crate_root
```
Canary release :
```text
release_v0_2_4_pre_008_completes_all_five_economics_wrappers_exactly_and_retry_safe
```
La canary globale :
```text
release_v0_2_4_descriptor_set_is_exact_and_remains_read_retry_safe_during_staged_wrappers
```
est renommée :
```text
release_v0_2_4_descriptor_set_is_exact_and_remains_read_retry_safe_through_wrapper_completion
```
car `pre.008` achève désormais les `15/15` wrappers `V0_2_4`.
Compteurs attendus après validation locale :
```text
240 unit tests
26 public API tests
21 release-completeness tests
1 smoke Devnet ignoré
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
```
## Fichiers ajoutés
```text
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.success.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.duplicates.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.single_null.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.empty.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.many_nulls.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.error_min_context.json
crates/ksp-onchain-transport-lib/fixtures/http/get_inflation_reward.error_epoch_rewards_active.json
deltas/0.2.4/pre.008.md
```
## Non-objectifs
Cette tranche ne réalise pas encore :
- le réaudit SIMD final `0180/0298/0301/0307/0385/0490/0550/0553`;
- le réaudit final de l'inventaire officiel HTTP;
- la preuve globale `52/52 wrappers current`;
- la preuve globale `14/14 historical Deprecated/runtime Removed`;
- les smokes de clôture;
- les graphes finaux de dépendances;
- la documentation de clôture et le prompt `0.2.5`.
Ces points restent réservés à `pre.009`.
## Validation attendue
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Résultat attendu :
```text
0 warning introduit
240 unit tests OK
26 public API tests OK
21 release-completeness tests OK
1 smoke Devnet ignoré
0 échec
```
## Commit attendu
```text
v0.2.4-pre.008
```
## Suite
`0.2.4-pre.009` reste la tranche de clôture technique/documentaire :
```text
réaudit SIMD HTTP final
réaudit inventaire officiel
compliance 52/52 + 14/14
KSP-TRANSPORT-007 final
smokes
graphes
documentation
prompt 0.2.5 — Wallet foundation
```

View File

@@ -0,0 +1,199 @@
<!-- file: deltas/0.2.4/pre.009-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.009-fix.001` — enrichissement du prompt `0.2.5` Wallet
## Type
Fix **documentation-only**.
Aucun changement Rust, Cargo, runtime, test, fixture HTTP ou contrat `0.2.4` n'est introduit.
Le `workspace.package.version` reste :
```text
0.2.4-pre.9
```
Le delta historique :
```text
deltas/0.2.4/pre.009.md
```
reste inchangé.
## Motif
Après validation de la candidate `0.2.4-pre.009`, le brainstorming préparatoire à `0.2.5 — Wallet foundation` a précisé des exigences importantes qui doivent être présentes dans le prompt de reprise avant l'ouverture de la nouvelle release.
Le prompt précédent couvrait déjà :
```text
audit crypto actuel
KDF / AEAD / CSPRNG
format versionné
persistence atomique
secret memory handling
signature
import/export
```
mais ne formalisait pas encore suffisamment :
```text
interopérabilité externe obligatoire du .kspwallet
threat model de bruteforce offline
absence de secret KSP/pepper propriétaire nécessaire à la récupération
Pubkey/alias/notes invisibles wallet verrouillé
alias interne indépendant du filename
notes protégées
sémantique exacte du format_version
paramètres KDF sérialisés
distinction éventuelle format_version / payload_version
deux capacités indépendantes VIEW / OWNER
architecture key slots / key wrapping
rotation indépendante des passwords sans changement de keypair
OWNER capable de recréer VIEW sans ancien password VIEW
question du read-only cryptographique de VIEW
test vectors publics pour implémentations externes
```
## Prompt mis à jour
`prompts/010-V0_2_5_START_PROMPT.md` passe :
```text
version 1 -> version 2
```
### Interopérabilité
Le `.kspwallet` doit être complètement spécifiable en dehors de `ksp-wallet-lib`.
La sécurité ne doit pas dépendre :
```text
du secret du format
d'un pepper global KSP
d'une donnée cachée dans le binaire
```
Une future spécification `KSPWALLET_V1` et des vecteurs de test publics doivent permettre l'écriture d'un déchiffreur compatible externe.
### Threat model
Le prompt impose désormais l'audit explicite de l'attaque offline :
```text
copie du fichier
-> essais de passwords sans serveur/rate-limit
-> KDF coûteux mais password faible toujours attaquable
```
et sépare les garanties apportées par :
```text
crypto du fichier
capabilities/types KSP
filesystem
éventuelle ancre de confiance externe
```
### Metadata protégée
Sans VIEW ou OWNER, le fichier ne doit pas exposer par défaut :
```text
Pubkey
alias
notes
```
L'alias reste indépendant du filename.
Les notes sont des metadata protégées, lisibles par VIEW/OWNER et administrables par OWNER.
### Versionnement
`format_version` est explicitement défini comme une version du **format**, non comme un compteur d'éditions.
Il ne change pas pour :
```text
rotation password
modification alias
modification notes
réécriture atomique sans changement de format
```
Un éventuel `payload_version` doit être séparé uniquement si le schéma protégé nécessite réellement une évolution indépendante.
### Capacités VIEW / OWNER
Le modèle cible devient :
```text
VIEW:
lire Pubkey/alias/notes
OWNER:
tout VIEW
signer
modifier alias/notes
gérer VIEW
changer OWNER
```
Invariants :
```text
VIEW et OWNER indépendants
OWNER ne dépend jamais de VIEW
VIEW compromis != secret Solana compromis
VIEW compromis != OWNER compromis
OWNER peut recréer/supprimer VIEW sans ancien VIEW password
rotation VIEW/OWNER != changement de keypair
```
Le prompt demande d'étudier une architecture de :
```text
content keys
key wrapping
key slots indépendants
```
plutôt qu'un double chiffrement naïf.
### Read-only de VIEW
L'API KSP doit obligatoirement rendre VIEW read-only.
Le gate `pre.001` doit aussi décider si V1 renforce cette propriété cryptographiquement contre un détenteur VIEW malveillant.
La préférence est de viser cette propriété si elle reste propre, interopérable et raisonnablement simple, avec une autorité metadata OWNER distincte de la keypair Solana sauf justification contraire.
Le prompt conserve explicitement la limite suivante :
```text
aucune information de confiance stockée uniquement dans le fichier
ne peut empêcher un attaquant ayant accès en écriture
de remplacer entièrement ce fichier par un autre fichier valide
```
## Fichiers
Modifié :
```text
prompts/010-V0_2_5_START_PROMPT.md
```
Ajouté :
```text
deltas/0.2.4/pre.009-fix.001.md
```
Aucun autre fichier n'est modifié.

402
deltas/0.2.4/pre.009.md Normal file
View File

@@ -0,0 +1,402 @@
<!-- file: deltas/0.2.4/pre.009.md -->
<!-- version: 1 -->
# Delta `0.2.4-pre.009` — clôture HTTP finale, compliance `52/52 + 14/14` et préparation Wallet
## Base requise
Livraison précédente validée :
```text
0.2.4-pre.008-fix.001
workspace.package.version = "0.2.4-pre.8.fix.1"
```
La validation locale fournie pour cette base est entièrement propre :
```text
cargo fmt --all -> terminé
cargo check --workspace -> terminé sans warning
cargo clippy --workspace --all-targets -> terminé sans warning
cargo test -p ksp-onchain-transport-lib -> 240 unit tests OK
26 public API tests OK
21 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Cette base inclut donc le fix purement Clippy de `pre.008-fix.001` sans modification fonctionnelle de `getInflationReward`.
## Objectif
`pre.009` est la prerelease de clôture de la série HTTP commencée en `0.2.1`.
Elle ne doit ajouter aucun nouveau comportement RPC standard. Elle doit au contraire prouver et documenter que la surface acquise est complète et cohérente :
```text
52 méthodes HTTP courantes officielles
52 wrappers typed KSP correspondants
14 méthodes historiques Deprecated / runtime Removed
14 descriptors historiques KSP correspondants
partition typed : 4 foundation + 22 Accounts/Tokens/Cluster + 11 Transactions + 15 Blocks/Economics
```
Elle doit également :
- réauditer la référence HTTP officielle et les SIMDs susceptibles de modifier cette surface ;
- figer les canaries finales de présence, de classification et de frontières ;
- étendre le smoke Transport pur aux nouvelles familles Blocks/Economics ;
- publier le document de conformité final `0.2.4` ;
- mettre à jour les documents vivants de la série ;
- fournir le prompt de démarrage de `0.2.5 — Wallet foundation` ;
- préparer `rel.001`, qui devra rester strictement publicationnel.
## Version Cargo
Nouvelle prerelease technique :
```text
0.2.4-pre.8.fix.1 -> 0.2.4-pre.9
```
Aucune dépendance, feature Cargo ou frontière de crate n'est modifiée.
## Réaudit officiel final de la surface HTTP
Le réaudit de la référence Solana HTTP courante retrouve exactement les `52` méthodes enregistrées par KSP.
La comparaison est effectuée sur les noms, pas seulement sur un compteur :
```text
officiel courant - registre KSP = {}
registre KSP - officiel courant = {}
```
Les `14` méthodes historiques présentées dans la navigation Deprecated officielle correspondent également exactement au registre historique KSP :
```text
officiel Deprecated - historique KSP = {}
historique KSP - officiel Deprecated = {}
```
Aucun nouveau RPC HTTP standard n'est donc introduit dans la release à cette date.
La baseline runtime de contrat utilisée pendant `0.2.4` reste :
```text
Agave v4.2.1
```
La rollout Agave `v4.2` est en cours pendant cette clôture ; cette prerelease ne transforme pas un calendrier de déploiement en affirmation d'activation cluster-wide.
## Réaudit SIMD final
Les statuts upstream observés pendant la clôture sont :
| SIMD | Sujet lié au HTTP KSP | Statut courant | Conséquence pour `0.2.4` |
|--------|---------------------------------------------|---------------------|---------------------------------------------------------------------------|
| `0180` | leader schedule keyed by vote account | `Review` | aucune nouvelle méthode HTTP officielle ; conserver le wire stable audité |
| `0298` | `bank_hash` dans le block footer | `Idea` | ne pas ajouter `bankHash` spéculativement |
| `0301` | remplacement proposé par `parent_bank_hash` | PR fermé, non mergé | aucune surface KSP |
| `0307` | block footer + extension `getBlock` | `Review` | pas de `footer` dans le contrat stable `v4.2.1` |
| `0385` | transaction V1 | `Review` | conserver `SolanaTransactionVersion::Number(u8)` générique |
| `0490` | Stake Program v5 / minimum delegation | `Review` | valeur fournie par le runtime, jamais codée en dur |
| `0550` | double disinflation | `Review` | valeurs inflation runtime, aucune formule KSP |
| `0553` | base inclusion + resource fee | `Draft` | évolution économique/fee ; aucun nouveau wrapper HTTP stable |
Les extensions wire déjà acquises sont également réauditées pour éviter qu'une clôture centrée sur la watchlist n'en perde une :
| SIMD | Statut courant | Contrat KSP confirmé |
|-----------------------------------------------|----------------|----------------------------------------------------------------------------------------------------------|
| `0118` Partitioned Epoch Rewards Distribution | `Activated` | `numRewardPartitions` reste lossless en omitted/null/value ; aucune synthèse locale |
| `0291` Commission Rate in Basis Points | `Review` | `commission` et `commissionBps` restent indépendants dans les rewards ; aucun calcul de conversion local |
Le statut `Review` de SIMD-0291 ne retire pas le wire déjà livré par Agave `v4.2.1`. Le réaudit ne justifie donc aucune extension wire spéculative dans `pre.009` ni aucune suppression d'un champ stable déjà acquis.
## Canarie public API finale
`tests/public_api.rs` ajoute :
```text
public_v0_2_4_pre_009_all_52_current_typed_wrappers_and_legacy_forms_are_available_from_crate_root
```
Cette canarie référence les `52` wrappers typed des méthodes courantes via `HttpTransportPool`.
Elle référence aussi séparément les deux formes legacy de requête qui ne constituent pas des méthodes RPC supplémentaires :
```text
get_block_legacy
get_transaction_legacy
```
Le but est de détecter immédiatement une suppression ou une invisibilité accidentelle d'un wrapper public pendant les futures refactorisations.
## Canarie release-completeness finale
`tests/release_completeness.rs` ajoute :
```text
release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact
```
Elle fige explicitement :
```text
52 noms courants exacts
14 noms historiques exacts
4 méthodes Foundation
22 méthodes V0_2_2
11 méthodes V0_2_3
15 méthodes V0_2_4
```
Elle vérifie également les invariants de classification :
```text
courant -> runtime Supported, documentation non Deprecated
Read -> RetrySafe
Simulation -> RetrySafe
WriteSubmission -> NeverAfterDispatch
historique -> Deprecated + Removed + Historical + NotApplicable
```
Cette preuve complète les canaries `KSP-TRANSPORT-007` déjà acquises ; elle ne les remplace pas.
## Compliance `KSP-TRANSPORT-007`
Le document final :
```text
docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
```
consolide la preuve `52/52` et les points sensibles accumulés depuis `0.2.1` :
- ordre et cardinalité des paramètres/réponses ;
- configs omises versus objets explicitement présents ;
- overloads modernes et historiques ;
- validations déterministes avant I/O ;
- préservation `omitted / null / value` ;
- listes positionnelles et ordre provider ;
- `getBlock` moderne + bare encoding legacy ;
- `transactionDetails` complet ;
- SIMD-0118 `numRewardPartitions` ;
- SIMD-0291 `commissionBps` ;
- version transaction numérique non nulle ;
- Economics runtime sans constantes locales inventées ;
- `getInflationReward` positionnel et cardinalité stricte.
## Smoke Transport pur final
Le smoke opt-in :
```text
crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs
```
reste :
- purement Transport ;
- settings construits programmatiquement ;
- read-only ;
- opt-in / ignoré par défaut ;
- indépendant de Config.
Il conserve les appels déjà présents Accounts/Tokens/Cluster/Transactions et ajoute trois appels peu fragiles :
```text
getBlockHeight(finalized)
getInflationRate()
getStakeMinimumDelegation(finalized)
```
Aucune valeur économique exacte n'est codée en dur. Le smoke vérifie uniquement des invariants de santé de réponse.
Le smoke de composition `Config -> Transport -> Devnet` reste une exception transitoire dans `ksp-config-lib` et doit toujours migrer vers une future surface dédiée d'intégration/orchestration/demo.
## Documentation finale `0.2.4`
Sont mis à jour :
```text
ROADMAP.md
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
docs/000-README.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
docs/validation/000-README.md
prompts/000-README.md
```
Le plan `011` passe en version `6` et enregistre le réaudit final sans modifier les contrats RPC déjà matérialisés.
Le raw RPC est désormais décrit comme une surface d'extension provider/diagnostic hors registre standard ; il ne constitue pas une alternative aux `52` wrappers typed standard.
`CHANGELOG.md` reste volontairement inchangé dans cette prerelease : la publication stable et son entrée finale appartiennent à `rel.001`.
## Document de validation final
Ajout :
```text
docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
```
Ce document sert de point de contrôle avant `rel.001`. Il contient notamment :
- le réaudit officiel `52 + 14` ;
- la matrice des `52` méthodes courantes ;
- la matrice des `14` historiques ;
- la partition `4 / 22 / 11 / 15` ;
- le réaudit SIMD ;
- les contrats sensibles Blocks/Economics ;
- les commandes de validation immédiate et finale ;
- les audits `cargo tree` de frontières ;
- les deux smokes Devnet opt-in ;
- les conditions de passage à `rel.001`.
## Prompt `0.2.5 — Wallet foundation`
Ajout :
```text
prompts/010-V0_2_5_START_PROMPT.md
```
Le prompt impose une `0.2.5-pre.001` d'audit + brainstorming + sizing avant toute implémentation cryptographique lourde.
Il cadre notamment :
```text
ksp-wallet-lib
format natif versionné .kspwallet
Wallet -> Core/Logging + primitives crypto/key low-level seulement
Wallet -X-> Config/Transport/Store/Tauri/ExecutionPolicy
pas de crypto maison
pas de secret dans Config
pas de WalletPolicy dans 0.2.5
pas de Wallet Desk dans 0.2.5
```
La sélection du KDF, de l'AEAD, du CSPRNG et de la stratégie de secret en mémoire n'est pas préjugée : elle doit être auditée contre les primitives actuelles lors de `pre.001`.
Le prompt exige également les invariants de sécurité principaux : mauvais mot de passe, tampering, format inconnu/tronqué, redaction, absence de secret en clair, changement de mot de passe sans changement de keypair, persistence atomique/no-clobber et signature vérifiable.
## Fichiers modifiés
```text
Cargo.toml
ROADMAP.md
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs
docs/000-README.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
docs/validation/000-README.md
prompts/000-README.md
```
## Fichiers ajoutés
```text
docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
prompts/010-V0_2_5_START_PROMPT.md
deltas/0.2.4/pre.009.md
```
Aucun fichier n'est supprimé.
## Validation immédiate attendue
Après application de l'overlay :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Compte attendu pour Transport :
```text
240 unit tests OK
27 public API tests OK
22 release-completeness tests OK
1 smoke Devnet ignoré comme prévu
0 échec
```
Le nombre de unit tests reste volontairement stable : `pre.009` ajoute des canaries d'intégration/public API et non une nouvelle logique RPC.
## Checkpoint final avant `rel.001`
Une fois la validation immédiate propre, exécuter aussi :
```bash
cargo test -p ksp-config-lib
cargo test -p ksp-core-lib
cargo test -p ksp-app-config-desk
cargo test --workspace
```
Audits de graphes :
```bash
cargo tree -p ksp-onchain-transport-lib -i ksp-config-lib
cargo tree -p ksp-onchain-transport-lib -i ksp-store-lib
cargo tree -p ksp-onchain-transport-lib -i ksp-program-lib
cargo tree -p ksp-onchain-transport-lib -i tracing
cargo tree -p ksp-config-lib -i ksp-onchain-transport-lib
cargo tree -p ksp-config-lib -i tracing
cargo tree -p ksp-onchain-transport-lib | grep -E 'solana-(client|rpc-client|rpc-client-api)'
cargo tree -p ksp-onchain-transport-lib | grep -E 'ksp-(config|store|program)'
```
Les commandes `cargo tree -i` qui signalent qu'un package n'existe pas dans le graphe sont acceptables lorsqu'elles démontrent précisément l'absence de dépendance recherchée ; le résultat doit être interprété et consigné, pas masqué. Pour `tracing`, une présence transitive sous `ksp-logging-lib` est conforme : l'interdit architectural porte sur une dépendance **directe** de Transport vers `tracing`, pas sur l'absence totale de `tracing` dans son graphe transitif.
Smokes réseau opt-in :
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapture
```
Le premier est le smoke Transport pur durable. Le second reste la composition transitoire `Config -> Transport` déjà documentée.
## Commit attendu
Après validation immédiate et checkpoint final propres :
```text
v0.2.4-pre.009
```
## Suite — `rel.001`
La livraison suivante doit être :
```text
0.2.4-rel.001
```
Elle doit être strictement publicationnelle :
- finaliser `CHANGELOG.md` et les statuts ROADMAP/documents de publication ;
- produire `deltas/0.2.4/rel.001.md` ;
- passer `workspace.package.version` de la prerelease candidate à `0.2.4` ;
- ne pas ajouter de wrapper, DTO, validation RPC ou comportement réseau ;
- rejouer les contrôles de publication nécessaires ;
- créer le tag stable `v0.2.4` après validation et commit de la release.
`0.2.5` ne commence qu'après cette publication stable.

185
deltas/0.2.4/rel.001.md Normal file
View File

@@ -0,0 +1,185 @@
<!-- file: deltas/0.2.4/rel.001.md -->
<!-- version: 1 -->
# Delta `v0.2.4-rel.001`
## Base
Base attendue :
```text
v0.2.4-pre.009-fix.001
```
Le fix `pre.009-fix.001` est documentation-only :
```text
workspace.package.version = "0.2.4-pre.9"
prompt 0.2.5 = version 2
```
Version Cargo cible :
```text
0.2.4
```
## Objectif
Publier `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale` sans ajouter de capacité fonctionnelle après `pre.009`.
`rel.001` est strictement publicationnelle :
- passage du workspace à la version stable `0.2.4` ;
- clôture `[X]` de `0.2.4` dans le ROADMAP ;
- ajout de l'entrée stable `0.2.4` au CHANGELOG ;
- clôture du plan `011` et de la matrice `validation/007` avec les preuves opérateur ;
- synchronisation des index, de l'inventaire composant et des README/USAGE Transport vers le statut stable ;
- conservation du prompt `prompts/010-V0_2_5_START_PROMPT.md` version 2 comme point d'ouverture de `0.2.5`.
Aucun fichier Rust de production, aucune API publique, aucune configuration runtime, aucune dépendance et aucune feature Cargo ne changent dans cette livraison.
## Surface stable publiée
La série HTTP `0.2.1``0.2.4` est complète :
```text
0.2.1 Foundation 4
0.2.2 Accounts/Tokens/Cluster 22
0.2.3 Transactions 11
0.2.4 Blocks/Economics 15
-------------------------------------
Current typed 52/52
Historical Deprecated/Removed 14/14
```
`0.2.4` publie exactement les dix wrappers Blocks et cinq wrappers Economics.
Points sensibles stabilisés notamment :
```text
getBlock moderne + bare encoding legacy deprecated
transactionDetails full/signatures/none/accounts
versions transaction numériques génériques
SIMD-0118 numRewardPartitions omitted/null/value
SIMD-0291 commission / commissionBps indépendants
overloads getBlocks et limites runtime auditées
getBlockProduction range/identity/context
Economics sans formule/minimum local codé en dur
getInflationReward ordre/doublons/null positionnels/cardinalité
```
La canary publique référence les 52 wrappers courants plus les deux formes legacy séparées `get_block_legacy` et `get_transaction_legacy`. La canary release fige les ensembles exacts 52 + 14 et leur partition `4/22/11/15`.
## Validation opérateur de la candidate
Les résultats fournis le **18 août 2026** confirment :
```text
cargo test -p ksp-config-lib OK
cargo test -p ksp-core-lib OK
cargo test -p ksp-app-config-desk OK
cargo test --workspace OK
```
Dans le workspace, Transport passe :
```text
240 unit
27 public API
22 release completeness
```
Les canaries Core de dépendances et les canaries Logging d'ownership passent également.
Les deux smokes Devnet opt-in ont été exécutés explicitement :
```text
Transport pur Accounts/Tokens/Cluster/Transactions/Blocks/Economics 1 passed
Config -> Transport -> foundation 1 passed
```
Le smoke Config -> Transport reste une exception transitoire cross-crates et devra migrer vers une future surface d'intégration/orchestration/demo lorsqu'elle existera.
Les sorties manuelles `cargo tree` demandées par la candidate ne figurent pas dans le journal opérateur fourni à la construction de `rel.001`. Elles ne sont donc pas marquées artificiellement comme exécutées ; la gate post-application ci-dessous les rejoue avant création du tag stable.
## Documentation de clôture
Mises à jour :
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
docs/000-README.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
docs/validation/000-README.md
docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
prompts/000-README.md
```
Ajout :
```text
deltas/0.2.4/rel.001.md
```
Le prompt `prompts/010-V0_2_5_START_PROMPT.md` reste inchangé par `rel.001` et conserve exactement la version 2 livrée par `pre.009-fix.001` : interopérabilité externe `.kspwallet`, threat model offline, metadata protégées, capacités VIEW/OWNER indépendantes, key slots/rotations à auditer et choix KDF/AEAD non préjugés avant `0.2.5-pre.001`.
Tous les deltas historiques `0.2.4` précédents restent inchangés.
## Commit et tag
Identifiant de commit attendu :
```text
v0.2.4-rel.001
```
Le tag stable ne doit être créé qu'après application et validation de ce delta :
```text
v0.2.4
```
## Validation finale après application
Gate stable :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
```
Puis dernière inspection des graphes :
```bash
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib -d
cargo tree -p ksp-onchain-transport-lib -e features
cargo tree -p ksp-onchain-transport-lib -e normal
cargo tree -p ksp-config-lib
cargo tree -p ksp-config-lib -d
cargo tree -p ksp-config-lib -e features
cargo tree -p ksp-config-lib -e normal
```
Vérifier en particulier :
```text
ksp-config-lib -> ksp-onchain-transport-lib
Transport -X-> Config/Store/Program/tracing direct
aucun solana-client/rpc-client haut niveau
```
Après succès : commit `v0.2.4-rel.001`, puis création du tag `v0.2.4`.
La session suivante s'ouvre avec `prompts/010-V0_2_5_START_PROMPT.md` et `0.2.5-pre.001` commence par audit officiel actuel + héritage utile + brainstorming + threat model + sizing avant implémentation cryptographique lourde.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md --> <!-- file: docs/000-README.md -->
<!-- version: 30 --> <!-- version: 33 -->
# Documentation KSP # Documentation KSP
@@ -42,7 +42,8 @@ docs/
│ ├── 007-V0_2_0_SERIES_PLANNING.md │ ├── 007-V0_2_0_SERIES_PLANNING.md
│ ├── 008-V0_2_1_ONCHAIN_HTTP_PLAN.md │ ├── 008-V0_2_1_ONCHAIN_HTTP_PLAN.md
│ ├── 009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md │ ├── 009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md
── 010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md ── 010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md
│ └── 011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
├── validation/ ├── validation/
│ ├── 000-README.md │ ├── 000-README.md
│ ├── 001-V0_1_4_CONFIG_DESKTOP.md │ ├── 001-V0_1_4_CONFIG_DESKTOP.md
@@ -50,7 +51,8 @@ docs/
│ ├── 003-V0_2_1_ONCHAIN_HTTP.md │ ├── 003-V0_2_1_ONCHAIN_HTTP.md
│ ├── 004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md │ ├── 004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md
│ ├── 005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md │ ├── 005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md
── 006-V0_2_3_HTTP_TRANSACTIONS.md ── 006-V0_2_3_HTTP_TRANSACTIONS.md
│ └── 007-V0_2_4_HTTP_FINAL_COMPLIANCE.md
└── rules/ └── rules/
├── FILE_CONTRACTS.md ├── FILE_CONTRACTS.md
├── PROMPT_STRUCTURE.md ├── PROMPT_STRUCTURE.md
@@ -67,7 +69,7 @@ D'autres sous-répertoires seront ajoutés uniquement lorsque leur rôle aura é
## Documents de planification ## Documents de planification
Le plan historique de la phase fondatrice clôturée est conservé dans [`plans/001-V0_0_3_PLAN.md`](plans/001-V0_0_3_PLAN.md). La séquence active des premières releases fonctionnelles est définie dans [`plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md`](plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md). Le plan détaillé de la release stable `0.1.1` est conservé comme historique clôturé dans [`plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md`](plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.2` est conservé comme historique clôturé dans [`plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`](plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.3 — Configuration foundation` est conservé comme historique clôturé dans [`plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.4 — ksp-app-config-desk` est conservé comme historique clôturé dans [`plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md`](plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md), avec sa matrice finale [`validation/001-V0_1_4_CONFIG_DESKTOP.md`](validation/001-V0_1_4_CONFIG_DESKTOP.md). Son prompt d'ouverture historique reste [`../prompts/004-V0_1_4_START_PROMPT.md`](../prompts/004-V0_1_4_START_PROMPT.md). La release stable `0.2.0` clôt l'audit de bot3 et le découpage de la série. Son plan directeur est conservé comme historique clôturé dans [`plans/007-V0_2_0_SERIES_PLANNING.md`](plans/007-V0_2_0_SERIES_PLANNING.md), avec sa matrice finale [`validation/002-V0_2_0_SERIES_PLANNING.md`](validation/002-V0_2_0_SERIES_PLANNING.md). La release stable `0.2.1 — HTTP Solana foundation` a été ouverte par [`../prompts/006-V0_2_1_START_PROMPT.md`](../prompts/006-V0_2_1_START_PROMPT.md). Son gate de sizing et sa matrice exhaustive sont conservés dans [`plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md), avec la validation finale [`validation/003-V0_2_1_ONCHAIN_HTTP.md`](validation/003-V0_2_1_ONCHAIN_HTTP.md), README/USAGE Transport et le smoke Devnet opt-in de composition Config -> Transport. Le prompt [`../prompts/007-V0_2_2_START_PROMPT.md`](../prompts/007-V0_2_2_START_PROMPT.md) a ouvert la release stable `0.2.2 — HTTP Accounts + Tokens + Cluster`. Son plan clôturé [`plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) conserve l'audit et l'implémentation des 22 wrappers typés, tandis que [`validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) enregistre les validations déterministes, les graphes Cargo et les deux smokes Devnet passés avant publication. Le prompt [`../prompts/008-V0_2_3_START_PROMPT.md`](../prompts/008-V0_2_3_START_PROMPT.md) a ouvert la release stable `0.2.3 — HTTP Transactions`. Son plan clôturé [`plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) conserve l'audit et l'implémentation des 11 wrappers ; le réaudit [`validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) confirme la complétude des 37 wrappers HTTP typés et [`validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](validation/006-V0_2_3_HTTP_TRANSACTIONS.md) enregistre les validations finales, graphes Cargo et deux smokes Devnet passés avant publication. Le prompt [`../prompts/009-V0_2_4_START_PROMPT.md`](../prompts/009-V0_2_4_START_PROMPT.md) ouvre `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`. Le plan historique de la phase fondatrice clôturée est conservé dans [`plans/001-V0_0_3_PLAN.md`](plans/001-V0_0_3_PLAN.md). La séquence active des premières releases fonctionnelles est définie dans [`plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md`](plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md). Le plan détaillé de la release stable `0.1.1` est conservé comme historique clôturé dans [`plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md`](plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.2` est conservé comme historique clôturé dans [`plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`](plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.3 — Configuration foundation` est conservé comme historique clôturé dans [`plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.4 — ksp-app-config-desk` est conservé comme historique clôturé dans [`plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md`](plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md), avec sa matrice finale [`validation/001-V0_1_4_CONFIG_DESKTOP.md`](validation/001-V0_1_4_CONFIG_DESKTOP.md). Son prompt d'ouverture historique reste [`../prompts/004-V0_1_4_START_PROMPT.md`](../prompts/004-V0_1_4_START_PROMPT.md). La release stable `0.2.0` clôt l'audit de bot3 et le découpage de la série. Son plan directeur est conservé comme historique clôturé dans [`plans/007-V0_2_0_SERIES_PLANNING.md`](plans/007-V0_2_0_SERIES_PLANNING.md), avec sa matrice finale [`validation/002-V0_2_0_SERIES_PLANNING.md`](validation/002-V0_2_0_SERIES_PLANNING.md). La release stable `0.2.1 — HTTP Solana foundation` a été ouverte par [`../prompts/006-V0_2_1_START_PROMPT.md`](../prompts/006-V0_2_1_START_PROMPT.md). Son gate de sizing et sa matrice exhaustive sont conservés dans [`plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md), avec la validation finale [`validation/003-V0_2_1_ONCHAIN_HTTP.md`](validation/003-V0_2_1_ONCHAIN_HTTP.md), README/USAGE Transport et le smoke Devnet opt-in de composition Config -> Transport. Le prompt [`../prompts/007-V0_2_2_START_PROMPT.md`](../prompts/007-V0_2_2_START_PROMPT.md) a ouvert la release stable `0.2.2 — HTTP Accounts + Tokens + Cluster`. Son plan clôturé [`plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) conserve l'audit et l'implémentation des 22 wrappers typés, tandis que [`validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) enregistre les validations déterministes, les graphes Cargo et les deux smokes Devnet passés avant publication. Le prompt [`../prompts/008-V0_2_3_START_PROMPT.md`](../prompts/008-V0_2_3_START_PROMPT.md) a ouvert la release stable `0.2.3 — HTTP Transactions`. Son plan clôturé [`plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) conserve l'audit et l'implémentation des 11 wrappers ; le réaudit [`validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) confirme la complétude des 37 wrappers HTTP typés et [`validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](validation/006-V0_2_3_HTTP_TRANSACTIONS.md) enregistre les validations finales, graphes Cargo et deux smokes Devnet passés avant publication. Le prompt [`../prompts/009-V0_2_4_START_PROMPT.md`](../prompts/009-V0_2_4_START_PROMPT.md) a ouvert la release stable `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`. Son plan clôturé [`plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) conserve limplémentation des 15 wrappers et la compliance `52/52 + 14/14`; la matrice finale [`validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) enregistre le réaudit SIMD/inventaire, les canaries globales et les preuves opérateur avant publication. Le prompt [`../prompts/010-V0_2_5_START_PROMPT.md`](../prompts/010-V0_2_5_START_PROMPT.md), finalisé par `0.2.4-pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`.
`IDEAS.md` conserve les pistes et questions qui ne sont pas encore des engagements du roadmap ni des décisions architecturales. `IDEAS.md` conserve les pistes et questions qui ne sont pas encore des engagements du roadmap ni des décisions architecturales.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md --> <!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 12 --> <!-- version: 14 -->
# Inventaire initial des composants KSP # Inventaire initial des composants KSP
@@ -18,13 +18,13 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
## Inventaire synthétique ## Inventaire synthétique
| Domaine | Composant | Type | Statut | Première cible actuelle | Mission | | Domaine | Composant | Type | Statut | Première cible actuelle | Mission |
|-------------------------|------------------------------------------|--------------------|--------------|---------------------------------|----------------------------------------------------------| |-------------------------|------------------------------------------|--------------------|--------------|---------------------------------|-------------------------------------------------------------|
| Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales | | Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales |
| Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP | | Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP |
| Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config | | Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config |
| Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config | | Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config |
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1` | foundation HTTP + 37 wrappers typés stables après 0.2.3 | | On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1``0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
| Wallet | `ksp-wallet-lib` | lib | Retenu | `0.2.5` | `.kspwallet`, secrets, signature, import/export | | Wallet | `ksp-wallet-lib` | lib | Retenu | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
| Wallet Desk | `ksp-app-wallet-desk` | app | Retenu | `0.2.6` | Wallet + Config composite + HTTP/balance | | Wallet Desk | `ksp-app-wallet-desk` | app | Retenu | `0.2.6` | Wallet + Config composite + HTTP/balance |
| Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.7` | WebSocket Solana complet, sessions/subscriptions | | Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.7` | WebSocket Solana complet, sessions/subscriptions |
| Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | LaserStream WebSocket comme extension du moteur standard | | Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | LaserStream WebSocket comme extension du moteur standard |
@@ -78,7 +78,7 @@ ksp-data-api
## Transport ## Transport
`ksp-onchain-transport-lib` doit couvrir l'intégralité des opérations documentées de la surface ciblée par chaque release. `0.2.1` stabilise la foundation HTTP et quatre wrappers typés canari. `0.2.2` stabilise 22 wrappers Accounts/Tokens/Cluster supplémentaires. `0.2.3` stabilise les 11 Transactions et porte la surface typed à 37 méthodes ; les 15 méthodes Blocks/Economics restent affectées à `0.2.4`, qui exécutera aussi la compliance finale de la surface 52 current + 14 historiques. Les statuts deprecated/obsolete encore fonctionnels et unstable/experimental restent exposés avec warning runtime KSP. `ksp-onchain-transport-lib` doit couvrir l'intégralité des opérations documentées de la surface ciblée par chaque release. `0.2.1` stabilise la foundation HTTP et quatre wrappers typés canari, `0.2.2` ajoute 22 wrappers Accounts/Tokens/Cluster et `0.2.3` stabilise les 11 Transactions. La release stable `0.2.4` ajoute les 10 Blocks + 5 Economics et atteint 52/52 méthodes HTTP courantes typées, avec 14/14 historiques Deprecated/Removed conservées pour compliance. Les statuts deprecated/obsolete encore fonctionnels et unstable/experimental restent exposés avec warning runtime KSP.
La Config standard Transport appartient à `ksp-config-lib`, qui adapte vers les settings publics du transport ; le transport ne dépend jamais de Config. La Config standard Transport appartient à `ksp-config-lib`, qui adapte vers les settings publics du transport ; le transport ne dépend jamais de Config.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/000-README.md --> <!-- file: docs/plans/000-README.md -->
<!-- version: 37 --> <!-- version: 40 -->
# Plans KSP # Plans KSP
@@ -19,6 +19,7 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou
- [`008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — plan de `0.2.1`, établi par `0.2.1-pre.001`, recalibré par `pre.001-fix.001` et amené en clôture candidate par `pre.007`; il conserve l'inventaire 52 méthodes HTTP courantes + 14 Deprecated historiques, le design Transport/Config et le split de couverture typée sur `0.2.1``0.2.4`. - [`008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — plan de `0.2.1`, établi par `0.2.1-pre.001`, recalibré par `pre.001-fix.001` et amené en clôture candidate par `pre.007`; il conserve l'inventaire 52 méthodes HTTP courantes + 14 Deprecated historiques, le design Transport/Config et le split de couverture typée sur `0.2.1``0.2.4`.
- [`009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) — plan clôturé de la release stable `0.2.2`, établi par `pre.001`, corrigé après réaudit Agave v4.2.1 puis exécuté jusqu'à `pre.007-fix.002`; il couvre les 22 wrappers Accounts/Tokens/Cluster, le smoke Transport opt-in et la préparation de `0.2.3`. - [`009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) — plan clôturé de la release stable `0.2.2`, établi par `pre.001`, corrigé après réaudit Agave v4.2.1 puis exécuté jusqu'à `pre.007-fix.002`; il couvre les 22 wrappers Accounts/Tokens/Cluster, le smoke Transport opt-in et la préparation de `0.2.3`.
- [`010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de la release stable `0.2.3 — HTTP Transactions`, ouvert par `pre.001`, exécuté jusqu'à `pre.009` puis publié par `rel.001`; il couvre les 11 méthodes, la classification `8 Read / 2 WriteSubmission / 1 Simulation`, `KSP-TRANSPORT-007`, le no-resend et la préparation de `0.2.4`. - [`010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de la release stable `0.2.3 — HTTP Transactions`, ouvert par `pre.001`, exécuté jusqu'à `pre.009` puis publié par `rel.001`; il couvre les 11 méthodes, la classification `8 Read / 2 WriteSubmission / 1 Simulation`, `KSP-TRANSPORT-007`, le no-resend et la préparation de `0.2.4`.
- [`011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan historique clôturé de la release stable `0.2.4`, ouvert par `pre.001`, exécuté jusquà `pre.009`, complété par le fix documentaire Wallet `pre.009-fix.001` puis publié par `rel.001`; il couvre les 10 Blocks + 5 Economics et la compliance finale `52/52 + 14/14` sous `KSP-TRANSPORT-007`.
Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre. Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md --> <!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
<!-- version: 39 --> <!-- version: 42 -->
# Séquence des releases fonctionnelles KSP # Séquence des releases fonctionnelles KSP
@@ -394,13 +394,15 @@ Chaque `pre.001` réaudite la documentation officielle actuelle. Les méthodes D
`0.2.3-pre.001` réaudite le 2026-08-18 la catégorie Transactions contre la documentation Solana actuelle et Agave v4.2.1 : les 11 méthodes prévues restent exactes, la classification `8 Read / RetrySafe`, `2 WriteSubmission / NeverAfterDispatch` et `1 Simulation / RetrySafe` reste correcte, et le gate de sizing est positif. `pre.002``pre.007` livrent ensuite les primitives wire puis les 11 wrappers, `pre.008` réaudite rétroactivement `KSP-TRANSPORT-007` sur les 37 wrappers HTTP typés sans remédiation fonctionnelle, et `pre.009` prépare la candidate finale avec documentation, smoke Transport read-only étendu et prompt `0.2.4`. `0.2.3-rel.001` publie cette surface stable après validation du workspace, des graphes Cargo Transport/Config et des deux smokes Devnet. Aucun `base64`, `bs58`, `wincode` ni client RPC Solana supplémentaire n'est ajouté : les payloads sérialisés restent opaques dans Transport tant qu'un besoin de décodage local n'est pas démontré. Le plan détaillé clôturé est `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`. `0.2.3-pre.001` réaudite le 2026-08-18 la catégorie Transactions contre la documentation Solana actuelle et Agave v4.2.1 : les 11 méthodes prévues restent exactes, la classification `8 Read / RetrySafe`, `2 WriteSubmission / NeverAfterDispatch` et `1 Simulation / RetrySafe` reste correcte, et le gate de sizing est positif. `pre.002``pre.007` livrent ensuite les primitives wire puis les 11 wrappers, `pre.008` réaudite rétroactivement `KSP-TRANSPORT-007` sur les 37 wrappers HTTP typés sans remédiation fonctionnelle, et `pre.009` prépare la candidate finale avec documentation, smoke Transport read-only étendu et prompt `0.2.4`. `0.2.3-rel.001` publie cette surface stable après validation du workspace, des graphes Cargo Transport/Config et des deux smokes Devnet. Aucun `base64`, `bs58`, `wincode` ni client RPC Solana supplémentaire n'est ajouté : les payloads sérialisés restent opaques dans Transport tant qu'un besoin de décodage local n'est pas démontré. Le plan détaillé clôturé est `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`.
`0.2.4-pre.001` réaudite le même jour l'inventaire HTTP officiel et la baseline runtime actuelle : les 15 méthodes réservées restent exactement 10 Blocks + 5 Economics, la navigation Deprecated reste à 14 historiques et Agave stable `v4.2.1` confirme les overloads/limites/extensions sensibles (`getBlock` legacy, `getBlocks`, plafond 500_000, performance samples 720, `commissionBps`). Le gate de sizing est positif sans split de release. `pre.002``pre.008` livrent ensuite les DTOs/wires et les 15 wrappers ; `pre.008-fix.001` corrige uniquement la conformité Clippy. `pre.009` réaudite l'index officiel et les SIMDs HTTP sensibles, confirme l'égalité exacte des ensembles 52 current + 14 Deprecated avec le registre, ajoute les canaries de wrapper/compliance globales, étend le smoke Transport aux familles Blocks/Economics et synchronise la documentation. `pre.009-fix.001` finalise ensuite le prompt Wallet sans changement Cargo. `0.2.4-rel.001` publie la surface stable après validation du workspace et des deux smokes Devnet. Le plan détaillé clôturé est `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`.
## `0.2.5` — Wallet foundation ## `0.2.5` — Wallet foundation
Mission : créer `ksp-wallet-lib` et le format `.kspwallet`. Mission : créer `ksp-wallet-lib` et le format `.kspwallet`.
Inclure protection du secret, pubkey/identity, signature, import/export extensible, changement de mot de passe et publication sûre. Inclure un format `.kspwallet` documenté et interopérable, un threat model dattaque offline, des capacités indépendantes `VIEW`/`OWNER`, Pubkey/alias/notes protégés lorsque verrouillé, key slots/rotations sans changement de keypair, signature sans exposition publique du secret brut et import/export extensible.
Exclure : temporary wallet JSON historique et `WalletPolicy`. `pre.001` doit auditer les choix KDF/AEAD/key wrapping/secret memory actuels avant de figer V1. Exclure : temporary wallet JSON historique et `WalletPolicy`.
## `0.2.6` — Wallet Desk ## `0.2.6` — Wallet Desk

View File

@@ -0,0 +1,572 @@
<!-- file: docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md -->
<!-- version: 7 -->
# Plan `0.2.4` — HTTP Blocks + Economics + compliance HTTP finale
## Statut
Ce plan ouvre `0.2.4` par `0.2.4-pre.001` sur la base stable attendue `v0.2.3`.
`0.2.1` a stabilisé la foundation HTTP et 4 wrappers typed, `0.2.2` les 22 wrappers Accounts/Tokens/Cluster et `0.2.3` les 11 wrappers Transactions. La surface acquise au démarrage est donc :
```text
52 méthodes HTTP courantes enregistrées
14 méthodes historiques Deprecated / runtime Removed
37 wrappers typed courants
15 wrappers typed restant sous HttpRpcCoverageRelease::V0_2_4
```
La mission de `0.2.4` est strictement de compléter les **10 Blocks + 5 Economics** déjà affectées à `V0_2_4`, puis de fermer la compliance HTTP globale **52/52 current + 14/14 historical** sous la règle `KSP-TRANSPORT-007`.
`0.2.4-rel.001` clôt ce plan sous statut **stable** : les 15/15 wrappers `V0_2_4` sont publiés, l'inventaire final reste 52 current + 14 Deprecated, les deux ensembles correspondent exactement au registre KSP et la matrice durable de clôture est `docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`.
## Gate de sizing `pre.001`
Question obligatoire :
```text
Les 15 wrappers Blocks/Economics, leurs DTOs/wires, les overloads/legacy,
les tests, la compliance 52+14 et la documentation peuvent-ils être
clôturés proprement dans cette release/session ?
```
Réponse :
```text
OUI.
```
Aucun split de release n'est nécessaire avant implémentation. Le volume est supérieur à `0.2.3` en nombre de wrappers mais reste inférieur à `0.2.2`. La complexité est surtout concentrée dans `getBlock`, `getBlockProduction`, `getInflationReward` et la compliance finale ; ces fils sont donc isolés dans des prereleases dédiées. Si une tranche réelle dépasse le budget KSP d'environ 1520 minutes, une prerelease supplémentaire sera créée **dans `0.2.4`** sans déplacer silencieusement une méthode vers `0.2.5`.
## Sources normatives réauditées le 2026-08-18
Documentation publique :
```text
https://solana.com/docs/rpc/http
https://solana.com/docs/rpc/json-structures
```
Pages Blocks :
```text
https://solana.com/docs/rpc/http/getblock
https://solana.com/docs/rpc/http/getblockcommitment
https://solana.com/docs/rpc/http/getblockheight
https://solana.com/docs/rpc/http/getblockproduction
https://solana.com/docs/rpc/http/getblocks
https://solana.com/docs/rpc/http/getblockswithlimit
https://solana.com/docs/rpc/http/getblocktime
https://solana.com/docs/rpc/http/getfirstavailableblock
https://solana.com/docs/rpc/http/getrecentperformancesamples
https://solana.com/docs/rpc/http/minimumledgerslot
```
Pages Economics :
```text
https://solana.com/docs/rpc/http/getinflationgovernor
https://solana.com/docs/rpc/http/getinflationrate
https://solana.com/docs/rpc/http/getinflationreward
https://solana.com/docs/rpc/http/getstakeminimumdelegation
https://solana.com/docs/rpc/http/getsupply
```
La documentation Solana actuelle relie encore plusieurs définitions/source examples à **Agave `v3.1.8`**. Ce pointeur documentaire n'est donc pas utilisé comme preuve que `v3.1.8` représente le runtime stable actuel.
Le cross-audit runtime a été effectué contre **Agave `v4.2.1`**, release patch publiée le 2026-08-13 dans la branche stable `4.2.x`. `v4.2.0` est explicitement publiée comme stable pour Mainnet Beta, Devnet et Testnet ; `v4.3.0-beta.0`, plus récente en date, reste une prerelease et n'est pas la baseline normative de cette release KSP.
Sources primaires Agave ciblées :
```text
https://github.com/anza-xyz/agave/releases/tag/v4.2.1
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc/src/rpc.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-types/src/config.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-types/src/response.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-api/src/custom_error.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/transaction-status-client-types/src/lib.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/transaction-status-client-types/src/option_serializer.rs
https://github.com/anza-xyz/agave/blob/v4.2.1/transaction-status/src/lib.rs
```
SIMDs primaires ciblés par l'audit `KSP-TRANSPORT-007` :
```text
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0096-reward-collected-priority-fee-in-entirety.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0118-partitioned-epoch-reward-distribution.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0123-block-revenue-distribution.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0180-vote-account-leader-schedule.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0185-vote-account-v4.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0186-loaded-transaction-data-size-specification.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0291-commission-rate-in-basis-points.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0298-bank-hash-in-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/pull/301
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0307-add-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0490-upgrade-stake-to-v5.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0550-double-disinflation.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0553-resource-fee-burn.md
```
## Inventaire officiel réaudité
L'index HTTP Solana actuel contient toujours exactement les catégories suivantes :
```text
Accounts 6
Tokens 5
Transactions 11
Blocks 10
Cluster 15
Economics 5
--
Current 52
```
La navigation Deprecated conserve les 14 noms historiques déjà enregistrés par KSP :
```text
confirmTransaction
getConfirmedBlock
getConfirmedBlocks
getConfirmedBlocksWithLimit
getConfirmedSignaturesForAddress2
getConfirmedTransaction
getFeeCalculatorForBlockhash
getFeeRateGovernor
getFees
getRecentBlockhash
getSignatureConfirmation
getSignatureStatus
getSnapshotSlot
getStakeActivation
```
La partition KSP reste donc inchangée :
```text
0.2.1 = 4
0.2.2 = 22
0.2.3 = 11
0.2.4 = 15
--
current = 52
historical = 14
```
Les 15 descriptors `V0_2_4` sont déjà classés `Read / RetrySafe`. `getBlock` reste le seul de ces descriptors marqué `StableWithDeprecatedLegacy` à cause de son second paramètre bare encoding encore accepté pour compatibilité.
## Règle de complétude `KSP-TRANSPORT-007`
Un wrapper de cette release n'est complet que s'il expose sans perte toutes les possibilités RPC retenues par l'audit : paramètres ordonnés, objets de config, overloads, formes legacy encore supportées, contraintes déterministes utiles, variantes de réponse et distinctions `omitted`/`null` nécessaires.
Pour `0.2.4`, cela impose notamment :
- de ne pas réduire `getBlocks` à la seule forme `[start, end, config]` ;
- de conserver le bare encoding legacy de `getBlock` sans le présenter comme forme recommandée ;
- de préserver `transactionDetails = full ou signatures ou none ou accounts` et les conséquences sur la présence des champs de bloc ;
- de préserver les champs wire riches des transactions/meta/rewards sans importer un client RPC Solana haut niveau ;
- de conserver les `null` positionnels de `getInflationReward` et le champ runtime `commissionBps` lorsqu'il est présent ;
- de ne pas inventer de limite d'adresses `getInflationReward` si aucune limite sémantique fixe n'est établie par la documentation/runtime courant ; la limite générale de taille de requête HTTP n'est pas transformée artificiellement en cardinalité métier KSP.
## Audit SIMD lié à `KSP-TRANSPORT-007`
Le statut d'un SIMD n'est pas, à lui seul, le critère d'implémentation de Transport. La règle est la suivante :
- si la baseline Solana/Agave stable expose déjà un paramètre, un champ ou une variante wire provenant d'une évolution SIMD, KSP doit le préserver même si le document SIMD n'a pas encore le statut `Activated` ;
- si un SIMD modifie seulement la sémantique d'une valeur calculée par le runtime sans modifier son wire, KSP ne doit ni recalculer ni figer cette valeur localement ;
- si un SIMD `Draft`/`Review` propose une extension RPC absente de la baseline stable retenue, KSP ne l'invente pas mais l'inscrit dans la watchlist et la réaudite avant le wrapper concerné puis à la compliance finale.
### SIMD applicables à la baseline `v4.2.1`
**SIMD-0118 — Partitioned Epoch Rewards Distribution** est `Activated` et a un impact direct sur `getBlock`. Le wire stable `UiConfirmedBlock` expose `numRewardPartitions` sous forme optionnelle. La future structure de bloc KSP doit donc :
- conserver `num_reward_partitions`/`numRewardPartitions` sans synthétiser `0` lorsqu'il est absent ;
- utiliser `SolanaWireField<u64>` ou une représentation équivalente capable de préserver au minimum l'omission, et de ne pas écraser un éventuel `null` de compatibilité ;
- couvrir par fixture un bloc avec `numRewardPartitions` présent et un bloc où le champ est omis ;
- ne pas déduire localement le nombre de partitions à partir des rewards ou de l'epoch.
**SIMD-0291 — Commission Rate in Basis Points** est encore en `Review`, mais Agave stable `v4.2.1` expose déjà les champs RPC de compatibilité correspondants. Il affecte trois surfaces KSP :
- `getVoteAccounts.inflationRewardsCommissionBps` : déjà couvert et testé dans la surface `0.2.2`; aucune remédiation n'est requise ;
- `getBlock.rewards[].commissionBps` : à couvrir dans `0.2.4` avec un reward wire de bloc dédié, distinct du reward d'inflation ;
- `getInflationReward[].commissionBps` : à couvrir dans `0.2.4` dans le DTO `SolanaInflationReward` ou nom final équivalent.
Pour les deux nouveaux wires de rewards, `commission` reste nullable et `commissionBps` doit préserver son caractère optionnel/omis. Les deux structures ne doivent pas être fusionnées artificiellement : le reward de bloc possède notamment `pubkey`, `lamports`, `postBalance` et `rewardType`, alors que le reward d'inflation possède `epoch`, `effectiveSlot`, `amount` et `postBalance`.
La surface `getTransaction` de `0.2.3` ne nécessite pas de correction SIMD-0291 : `meta` est déjà conservé losslessly en `SolanaWireField<serde_json::Value>`, ce qui préserve les rewards imbriqués et leurs extensions sans décodage métier.
### SIMDs déjà absorbés ou sans nouveau wire HTTP
**SIMD-0185 — Vote Account v4** est `Accepted`. Les nouvelles données de vote account peuvent faire évoluer les sous-arbres retournés par les parsers `jsonParsed`, mais `SolanaParsedAccountData.parsed` reste un `serde_json::Value` lossless. Agave `v4.2.1` n'ajoute pas les collector fields de Vote Account v4 au DTO dédié `RpcVoteAccountInfo`; aucune extension spéculative de `SolanaVoteAccountInfo` n'est donc ajoutée maintenant.
**SIMD-0186 — Loaded Transaction Data Size Specification** est `Accepted` et précise la sémantique consensus de la taille de données chargées. La surface `simulateTransaction` de `0.2.3` expose déjà `loadedAccountsDataSize` via `SolanaWireField<u32>` et couvre son omission : aucune nouvelle structure n'est requise. Transport ne doit pas recalculer cette valeur à partir des comptes de la transaction ; il restitue le résultat runtime.
**SIMD-0385 — Transaction V1 Format** est encore en `Review`. Agave `v4.2.1` possède déjà du plumbing V1 dans ses types transaction-status, mais le contrat KSP acquis est volontairement générique : `SolanaTransactionVersion::Number(u8)` n'est pas limité à `0`, et les payloads JSON restent lossless. `0.2.4` doit conserver cette propriété dans `getBlock` et ajouter une canary déterministe avec une version numérique non nulle afin d'interdire une régression vers un modèle `legacy ou v0` seulement.
**SIMD-0096 — Reward full priority fee to validator** est `Activated`, mais change la distribution économique des priority fees sans changer le wire HTTP que KSP doit décoder. Transport continue à restituer les valeurs runtime ; aucune structure spécifique n'est ajoutée.
**SIMD-0550 — Double Disinflation Rate** est en `Review` et précise explicitement que `getInflationRate` et `getInflationGovernor` refléteront la nouvelle schedule sans changement d'API. KSP ne doit donc figer ni `initial`, ni `taper`, ni le taux courant : les DTOs restent des valeurs runtime `f64` et aucune formule d'inflation n'est réimplémentée dans Transport. Un réaudit avant la tranche Economics et à la clôture vérifie qu'aucun nouveau champ n'a été ajouté par la baseline stable.
### Watchlist obligatoire pendant `0.2.4`
**SIMD-0180 — Vote Account Address Keyed Leader Schedule** est en `Review`. Son volet RPC demande explicitement que les endpoints historiques de leader schedule et slot leader continuent à retourner l'identité validator afin de préserver la compatibilité, tout en prévoyant de nouveaux endpoints vote-account-keyed. Le wrapper KSP `getLeaderSchedule` acquis en `0.2.2` ne doit donc pas être modifié spéculativement. En revanche, le réaudit d'inventaire de `pre.009` doit détecter si de nouveaux endpoints ont rejoint la surface HTTP officielle : ce serait alors une évolution de l'inventaire RPC, pas un changement silencieux du wire de `getLeaderSchedule`.
**SIMD-0307 — Add Block Footer** est en `Review` et propose précisément une extension de `getBlock` : option de config `footer` et champs footer dans la réponse. Ces éléments sont absents de `RpcBlockConfig` et `UiConfirmedBlock` dans Agave stable `v4.2.1`; ils ne doivent donc pas être inventés. **SIMD-0298 — Add `bank_hash` to block footer** reste au statut documentaire `Idea` et prévoit d'étendre ce même footer avec `bank_hash`. Le réaudit `pre.006` du 18 août 2026 confirme que le wire RPC stable `v4.2.1` n'expose toujours aucun de ces champs. Le tracker Bankless Leader amont signale néanmoins l'implémentation Agave du mécanisme de block footer/bank hash pour la future migration Alpenglow : cela renforce la nécessité du réaudit final sans justifier une extension spéculative du wrapper actuel.
**SIMD-0301 — Replace `bank_hash` with `parent_bank_hash`** est également surveillé comme évolution directement dépendante de ce footer. La proposition amont a été fermée sans merge le 28 janvier 2026; elle n'est donc pas un contrat SIMD adopté ni un champ RPC stable. `pre.009` doit réauditer ensemble `0298`, `0301` et `0307`. Si une baseline stable expose alors le footer, `KSP-TRANSPORT-007` impose de couvrir toutes ses options et tous ses champs effectivement livrés, sans supposer à l'avance si le hash final est `bankHash`, `parentBankHash` ou absent.
**SIMD-0490 — Upgrade BPF Stake Program to v5.0.0** est en `Review` et prévoit notamment une hausse du minimum de délégation ainsi que son exposition par le RPC de minimum delegation. Pour `getStakeMinimumDelegation`, KSP doit donc traiter la valeur contextualisée comme une valeur runtime opaque en lamports : aucun minimum `1 lamport`, `1 SOL` ou autre ne doit être codé en dur ou validé côté Transport. Le SIMD sera réaudité avant `pre.007` et à `pre.009`.
**SIMD-0553 — Base Inclusion and Resource-based Fee** est en `Draft`. Il change la sémantique du total de fees de `getFeeForMessage`, `simulateTransaction` et `getTransaction.meta.fee` sans imposer actuellement de changement de forme JSON. Les wrappers `0.2.3` doivent donc rester pass-through/lossless et ne pas recalculer le fee. La compliance finale `52/52` réauditera cette proposition afin de détecter une éventuelle extension stable de schéma apparue pendant la release.
Les SIMDs `Review` liés au block-revenue sharing, notamment SIMD-0123, peuvent modifier à terme la provenance/sémantique des rewards, mais n'ajoutent pas de champ HTTP stable dans la baseline `v4.2.1`. Le reward wire de KSP doit donc rester fidèle aux champs effectivement retournés et ne pas encoder une taxonomie économique non présente sur le wire.
## Matrice Blocks — 10 wrappers
| Méthode | Requête à couvrir | Résultat à préserver | Contraintes / décisions `KSP-TRANSPORT-007` |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getBlock` | `slot`; second paramètre absent, bare encoding legacy, ou config `{commitment, encoding, transactionDetails, maxSupportedTransactionVersion, rewards}` | `object ou null`; `previousBlockhash`, `blockhash`, `parentSlot`, transactions/signatures/rewards selon config, `numRewardPartitions`, `blockTime`, `blockHeight` | commitment runtime au moins `confirmed`; réutiliser les encodings/version/transaction wire de `0.2.3`; préserver omission vs null; `numRewardPartitions` suit SIMD-0118; les rewards préservent `commissionBps` de SIMD-0291; garder les erreurs runtime de bloc/version comme erreurs RPC, sans les masquer |
| `getBlockCommitment` | `slot` uniquement | `{commitment: array<u64> ou null, totalStake: u64}` | aucune config à inventer; préserver commitment nullable |
| `getBlockHeight` | config contextuelle optionnelle `{commitment, minContextSlot}` | `u64` | réutiliser `SolanaContextConfig`; `minContextSlot` est transmis tel quel au runtime |
| `getBlockProduction` | config optionnelle `{commitment, identity, range:{firstSlot,lastSlot?}}` | `SolanaRpcResponse<{byIdentity, range}>` | `identity` typée Pubkey; validation locale déterministe `lastSlot >= firstSlot` lorsque les deux sont fournis; préserver les couples `[leaderSlots, blocksProduced]` |
| `getBlocks` | `[start]`, `[start,end]`, `[start,config]`, `[start,end,config]`; config `{commitment,minContextSlot}` | `Vec<u64>` | commitment au moins `confirmed`; transmettre `minContextSlot`; conserver l'overload untagged du second paramètre; `end < start` donne `[]`; rejeter localement une différence `end-start > 500_000` |
| `getBlocksWithLimit` | `start`, `limit`, config contextuelle optionnelle `{commitment,minContextSlot}` | `Vec<u64>` | commitment au moins `confirmed`; transmettre `minContextSlot`; `limit <= 500_000`; `limit = 0` est valide et donne `[]` |
| `getBlockTime` | `slot` uniquement | `i64 ou null` | préserver l'absence de timestamp; les cas cleaned/skipped/not available restent des erreurs RPC runtime, pas des valeurs synthétiques |
| `getFirstAvailableBlock` | aucun paramètre | `u64` | requête exacte `params: []`; aucune config |
| `getRecentPerformanceSamples` | `limit?` | tableau de `{slot,numTransactions,numSlots,samplePeriodSecs,numNonVoteTransactions?}` | défaut runtime `720`; maximum `720`; rejeter avant I/O `limit > 720`; préserver `numNonVoteTransactions` nullable/ancien runtime |
| `minimumLedgerSlot` | aucun paramètre | `u64` | requête exacte `params: []`; conserver les erreurs ledger/runtime |
### Clarification runtime `pre.004` — config des ranges Blocks
Le réaudit dimplémentation de `pre.004` confirme dans Agave `v4.2.1` que `getBlocks` et `getBlocksWithLimit` consomment tous deux `RpcContextConfig`, et non un simple objet commitment. Le wrapper `RpcBlocksConfigWrapper` permet en plus à `getBlocks` de recevoir soit `end_slot`, soit cette config en deuxième position.
KSP doit donc réutiliser `SolanaContextConfig` et préserver les deux champs stables :
```text
commitment
minContextSlot
```
La documentation publique naffiche actuellement que `commitment` sur ces pages ; la source runtime stable prime ici conformément à `KSP-TRANSPORT-007`.
### `getBlock` — stratégie wire
`getBlock` est le plus gros fil de la release et reçoit une prerelease dédiée.
Les primitives déjà acquises de `rpc_transactions` doivent être réutilisées lorsque le wire est réellement commun :
```text
SolanaTransactionEncoding
SolanaEncodedTransaction
SolanaTransactionVersion
SolanaWireField<T>
```
`SolanaConfirmedTransaction` ne doit **pas** être réutilisé tel quel pour un élément de bloc : ce DTO contient `slot` et `blockTime`, qui appartiennent au résultat top-level de `getTransaction` et non à chaque élément `transactions[]` d'un bloc. Un type wire de transaction de bloc dédié peut en revanche composer les primitives ci-dessus et préserver `transaction`, `meta` et `version` sans décodage Program.
Les champs top-level dont la présence dépend de la config (`transactions`, `signatures`, `rewards`, `numRewardPartitions`) doivent utiliser une représentation capable de distinguer une omission d'un `null` lorsqu'une telle distinction existe sur le wire. `numRewardPartitions` est explicitement rattaché à SIMD-0118 et doit être couvert en présent/omis. Le reward wire de bloc doit conserver `commission` nullable et `commissionBps` optionnel/omis de SIMD-0291. Les transactions `accounts` ne doivent pas être forcées dans une structure `full` plus riche que le wire réellement retourné. Une version de transaction numérique différente de `0` doit rester représentable afin de ne pas régresser la compatibilité préparée pour SIMD-0385.
## Matrice Economics — 5 wrappers
| Méthode | Requête à couvrir | Résultat à préserver | Contraintes / décisions `KSP-TRANSPORT-007` |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getInflationGovernor` | config commitment optionnelle | `{initial,terminal,taper,foundation,foundationTerm}` en `f64` | réutiliser `SolanaCommitmentConfig`; aucun contexte de réponse |
| `getInflationRate` | aucun paramètre | `{total,validator,foundation,epoch}` | requête exacte `params: []` |
| `getInflationReward` | liste ordonnée d'adresses; config optionnelle `{epoch,commitment,minContextSlot}` | `Vec<Option<Reward>>` positionnel | commitment >= `confirmed`; ordre/cardinalité identiques aux entrées; préserver `commission: u8 ou null` et `commissionBps: u16` optionnel/omis de Agave `v4.2.1` conformément à SIMD-0291; aucune limite fixe d'adresses inventée |
| `getStakeMinimumDelegation` | config contextuelle optionnelle `{commitment,minContextSlot}` | `SolanaRpcResponse<u64>` | réutiliser `SolanaContextConfig` / `SolanaRpcResponse<T>`; restituer la valeur runtime en lamports sans minimum codé en dur; surveiller SIMD-0490 |
| `getSupply` | config optionnelle `{commitment,excludeNonCirculatingAccountsList}` | `SolanaRpcResponse<{total,circulating,nonCirculating,nonCirculatingAccounts}>` | le booléen runtime par défaut est `false`; préserver la liste ordonnée retournée lorsqu'elle est demandée |
### Extension runtime `commissionBps`
La documentation publique actuelle de `getInflationReward` énumère encore uniquement `commission`. Agave `v4.2.1` définit cependant aussi :
```text
commission_bps: Option<u16>
```
sérialisé en `commissionBps` lorsqu'il est présent. Ce champ correspond à l'évolution SIMD-0291 et doit être conservé dans le DTO KSP pour ne pas perdre une capacité/extension du runtime stable courant. Le DTO doit préserver séparément `commission` nullable et `commissionBps` optionnel/omis; il ne doit pas convertir les basis points en pourcentage ni dériver l'un des deux champs depuis l'autre.
## Contraintes locales retenues
Les validations déterministes suivantes doivent être réalisées avant I/O :
```text
getBlock commitment >= confirmed
getBlocks commitment >= confirmed
getBlocks end-start <= 500_000 lorsqu'end >= start
getBlocksWithLimit commitment >= confirmed
getBlocksWithLimit limit <= 500_000
getRecentPerformanceSamples limit <= 720
getBlockProduction lastSlot >= firstSlot lorsqu'ils sont tous deux présents
getInflationReward commitment >= confirmed
```
`getBlocks(end < start)` et `getBlocksWithLimit(limit = 0)` sont des cas valides qui doivent produire un tableau vide plutôt qu'une erreur locale.
Ne pas reproduire localement les validations qui nécessiteraient un décodage disproportionné ou l'état du ledger : disponibilité réelle d'un bloc, slot cleaned/skipped, support effectif d'une version de transaction par le caller, présence historique des metadata, epoch disponible, minContextSlot atteint, etc. Ces cas restent des erreurs/réponses RPC validées par le flux central.
## Réutilisation des contrats existants
### À réutiliser directement
```text
SolanaCommitment
SolanaCommitmentConfig
SolanaContextConfig
SolanaRpcContext
SolanaRpcResponse<T>
SolanaTransactionEncoding
SolanaEncodedTransaction
SolanaTransactionVersion
SolanaWireField<T>
```
### À introduire dans `0.2.4`
Noms définitifs ajustables pendant l'implémentation, responsabilités fixes :
```text
config getBlock moderne + enum transactionDetails
config/range/result getBlockProduction
block commitment result
confirmed block + transaction-in-block + reward wire, avec `numRewardPartitions` SIMD-0118 et `commissionBps` SIMD-0291
performance sample
inflation governor/rate/reward, avec reward d'inflation distinct et `commissionBps` SIMD-0291
supply result/config
```
La déduplication ne doit pas fusionner des contrats ayant des sémantiques différentes uniquement parce que leurs champs se ressemblent.
## Architecture d'exécution
Les 15 wrappers restent `Read / RetrySafe` et passent exclusivement par :
```text
wrapper typed
-> descriptor central
-> execute_standard_rpc
-> pool/admission
-> executor HTTP
-> validation JSON-RPC
-> decode typed
```
Interdits :
```text
client HTTP parallèle
reqwest direct dans un wrapper
retry/deadline/admission bypass
Transport -> Config
Transport -> Store/Program
tracing direct
solana-rpc-client / solana-client pour masquer les possibilités RPC
```
## Dépendances
Aucune nouvelle dépendance n'est nécessaire au vu de l'audit `pre.001`.
En particulier :
```text
solana-rpc-client NON
solana-client NON
crate SDK Block NON
base64/bs58 NON pour cette release
```
Les primitives `serde`/`serde_json`, `ksp-core-lib::Pubkey` et les wires Transaction déjà présents suffisent à exprimer les contrats sans introduire un SDK RPC haut niveau.
## Tests déterministes attendus
Chaque wrapper doit couvrir :
- request JSON exacte ;
- absence de config et objet vide lorsque ces deux formes sont significatives ;
- tous les overloads/configs/encodings pertinents ;
- réponse typed nominale ;
- `null` et omission lorsque le wire les distingue ;
- ordre et cardinalité ;
- erreur JSON-RPC propagée ;
- validation déterministe avant I/O lorsqu'elle est retenue par ce plan.
Cas de régression obligatoires spécifiques :
```text
getBlock bare encoding legacy
getBlock transactionDetails full/signatures/none/accounts
getBlock result null
getBlock numRewardPartitions présent/omis (SIMD-0118)
getBlock rewards commission nullable + commissionBps présent/omis (SIMD-0291)
getBlock transaction version numérique non nulle conservée (canary SIMD-0385)
getBlocks [start,config] vs [start,end,config] + minContextSlot transmis
getBlocks range > 500_000 rejetée avant I/O
getBlocksWithLimit 0 et > 500_000 + minContextSlot transmis
getRecentPerformanceSamples défaut/720/>720
getBlockProduction lastSlot < firstSlot rejeté avant I/O
getInflationReward ordre + null positionnels + commission/commissionBps présent/omis (SIMD-0291)
getStakeMinimumDelegation valeur runtime arbitraire sans minimum codé en dur (watch SIMD-0490)
getSupply excludeNonCirculatingAccountsList true/false/omitted
```
## Compliance finale HTTP
La dernière prerelease doit ajouter une preuve explicite que les **52 méthodes courantes enregistrées** possèdent désormais toutes un wrapper typed public réel. Un appel raw/générique ne compte pas.
Canaries finales :
```text
current registry == 52
historical registry == 14
V0_2_1 exact == 4
V0_2_2 exact == 22
V0_2_3 exact == 11
V0_2_4 exact == 15
typed wrappers current == 52/52
historical Deprecated/Removed == 14/14
KSP-TRANSPORT-007 audited current == 52/52
```
La compliance `KSP-TRANSPORT-007` des 37 wrappers de `0.2.1``0.2.3` reste acquise par `docs/validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`; la clôture `0.2.4` vérifie qu'aucune régression n'a été introduite et applique la même règle aux 15 nouveaux wrappers. Le réaudit final inclut explicitement les extensions wire déjà acquises SIMD-0118 et SIMD-0291, puis l'état des SIMD susceptibles de modifier la surface HTTP ou sa sémantique observable, au minimum SIMD-0180, SIMD-0298, SIMD-0301, SIMD-0307, SIMD-0385, SIMD-0490, SIMD-0550 et SIMD-0553, afin qu'une évolution devenue stable pendant la session ne soit pas oubliée. Il réaudite aussi l'inventaire officiel lui-même afin de détecter d'éventuels nouveaux endpoints issus de SIMD-0180 ou d'une autre évolution d'interface.
## Réaudit final `pre.009`
Le réaudit du **18 août 2026** confirme l'index HTTP officiel actuel sans recalibrage :
```text
Solana current HTTP exact names == KSP current registry exact names == 52
Solana Deprecated exact names == KSP historical exact names == 14
missing == 0
extra == 0
```
La navigation officielle actuelle ne contient aucun nouvel endpoint HTTP issu de SIMD-0180.
État final de la watchlist prospective :
```text
SIMD-0180 Review
SIMD-0298 Idea
SIMD-0301 PR closed / unmerged
SIMD-0307 Review
SIMD-0385 Review
SIMD-0490 Review
SIMD-0550 Review
SIMD-0553 Draft
```
Extensions SIMD déjà matérialisées dans le wire stable et réauditées à la clôture :
```text
SIMD-0118 Activated -> numRewardPartitions préservé en omitted/null/value
SIMD-0291 Review -> commission et commissionBps préservés indépendamment
```
Le statut `Review` de SIMD-0291 ne retire pas le champ déjà exposé par Agave `v4.2.1` : `KSP-TRANSPORT-007` impose de conserver ce wire réellement livré. SIMD-0118 est `Activated` et confirme définitivement que `numRewardPartitions` fait partie des extensions de bloc à ne pas aplatir ni synthétiser.
Conséquences :
- aucun changement anticipé de `getLeaderSchedule` pour SIMD-0180 ;
- aucun `footer`, `bankHash` ou `parentBankHash` spéculatif dans `getBlock` ;
- `SolanaTransactionVersion::Number(u8)` reste générique ;
- minimum de délégation et inflation restent des valeurs runtime ;
- SIMD-0553 ne crée aucun nouveau contrat HTTP stable dans la baseline auditée.
La preuve typed finale est doublée :
```text
public API canary -> référence les 52 wrappers current + 2 wrappers legacy séparés
release canary -> fige noms exacts 52 + 14 et partition 4/22/11/15
```
La matrice complète est conservée dans `docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`.
## Smokes live
Les smokes restent opt-in et secondaires par rapport aux fixtures locales.
`pre.009` étend le smoke Transport pur avec `getBlockHeight`, `getInflationRate` et `getStakeMinimumDelegation`. Ces reads restent robustes : aucun slot historique fixe, aucun compte reward spécifique et aucune valeur économique codée en dur. Éviter un `getBlock` dépendant d'un slot fixe ou tout scénario fragile lié à des récompenses d'un epoch précis.
Le smoke historique Config -> Transport reste une exception transitoire de composition et ne devient pas la destination générale des futurs scénarios cross-crates.
## Prévision souple des prereleases
```text
pre.001 audit officiel actuel + Agave stable + matrice + wires + contraintes + sizing
pre.002 primitives/configs/results Blocks/Economics partagés + reward wires SIMD-0118/0291
+ fixtures communes
pre.003 Blocks simples : getBlockCommitment, getBlockHeight, getBlockTime,
getFirstAvailableBlock, minimumLedgerSlot
pre.004 ranges/performance : getBlocks, getBlocksWithLimit, getRecentPerformanceSamples
pre.005 getBlockProduction + range/identity/result contextualisé
pre.006 réaudit SIMD-0298/0307 + getBlock moderne + bare encoding legacy + transactionDetails
+ wire bloc riche + SIMD-0118/0291 + canary version numérique SIMD-0385
pre.007 réaudit SIMD-0490/0550 + Economics simples : getInflationGovernor, getInflationRate,
getStakeMinimumDelegation, getSupply
pre.008 réaudit commitment runtime + getInflationReward + null positionnels + commissionBps SIMD-0291 + invariants
pre.009 réaudit SIMD HTTP (0118/0180/0291/0298/0301/0307/0385/0490/0550/0553 minimum)
+ réaudit inventaire officiel + compliance finale 52/52 + 14/14
+ KSP-TRANSPORT-007 + docs + smokes + graphes + prompt 0.2.5 — Wallet foundation
rel.001 publication strictement publicationnelle
```
Cette prévision peut être redécoupée si une tranche dépasse le budget réel. Elle ne doit pas être compressée artificiellement pour conserver un numéro de prerelease prévu.
## Hors périmètre
```text
Wallet / .kspwallet
WebSocket
LaserStream
Yellowstone gRPC
Store/Program/Materializer
modèle métier de transaction/bloc
client RPC Solana haut niveau
réécriture des deltas historiques
```
## Critères de clôture `0.2.4`
- 15/15 wrappers `V0_2_4` publics, typed et `KSP-TRANSPORT-007` conformes ;
- 52/52 méthodes HTTP courantes avec wrapper typed ;
- 14/14 historiques conservées en compliance ;
- aucun bypass du flux central Transport ;
- aucune nouvelle dépendance injustifiée ;
- tests déterministes complets ;
- smokes live pertinents validés par preuve opérateur ;
- graphes Cargo Transport/Config réaudités ;
- README/USAGE/matrices/validation synchronisés ;
- prompt `0.2.5 — Wallet foundation` finalisé ;
- dernière prerelease candidate complète ;
- `0.2.4-rel.001` strictement publicationnelle.
## Publication stable `rel.001`
La candidate `pre.009`, complétée par le fix documentaire `pre.009-fix.001`, a reçu les preuves opérateur de clôture suivantes le **18 août 2026** :
```text
cargo test -p ksp-config-lib OK
cargo test -p ksp-core-lib OK
cargo test -p ksp-app-config-desk OK
cargo test --workspace OK
Transport dans le workspace :
240 unit passed
27 public API passed
22 release completeness passed
smoke Devnet Transport pur 1 passed
smoke Devnet Config -> Transport 1 passed
```
Les canaries workspace de dépendances et downership Logging passent également dans `cargo test --workspace`. Les sorties manuelles `cargo tree` nétant pas présentes dans le journal opérateur fourni à la publication, elles ne sont pas déclarées artificiellement comme exécutées ; les contrôles de graphe restent à rejouer avec la gate finale post-application de `rel.001` avant création du tag.
`rel.001` ne modifie aucun wrapper, DTO, comportement réseau, dépendance ou feature Cargo : il passe uniquement le workspace à `0.2.4` stable et synchronise la documentation de publication.
## Questions ouvertes
Aucune question fonctionnelle ou wire bloquante pour `0.2.4`.
Les choix fonctionnels et wire de `0.2.4` sont figés. Toute nouvelle évolution officielle détectée après publication appartient à une release ultérieure, sauf défaut de conformité démontré nécessitant un correctif stable explicite.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/000-README.md --> <!-- file: docs/validation/000-README.md -->
<!-- version: 9 --> <!-- version: 11 -->
# Validations KSP # Validations KSP
@@ -16,3 +16,4 @@ Documents :
- [`005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) — réaudit rétroactif de complétude `KSP-TRANSPORT-007` des 37 wrappers HTTP typés livrés de `0.2.1` à `0.2.3-pre.007`, avec preuves Solana/Agave et verdict méthode par méthode. - [`005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) — réaudit rétroactif de complétude `KSP-TRANSPORT-007` des 37 wrappers HTTP typés livrés de `0.2.1` à `0.2.3-pre.007`, avec preuves Solana/Agave et verdict méthode par méthode.
- [`006-V0_2_3_HTTP_TRANSACTIONS.md`](006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée de `0.2.3`, 11 wrappers Transactions, sécurité write/simulation, réaudit 52+14, `KSP-TRANSPORT-007` 37/37, graphes Cargo et deux smokes Devnet passés avant publication stable. - [`006-V0_2_3_HTTP_TRANSACTIONS.md`](006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée de `0.2.3`, 11 wrappers Transactions, sécurité write/simulation, réaudit 52+14, `KSP-TRANSPORT-007` 37/37, graphes Cargo et deux smokes Devnet passés avant publication stable.
- [`007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) — matrice finale validée de `0.2.4`, inventaire exact 52 current + 14 Deprecated, preuve typed 52/52, audit SIMD final, `KSP-TRANSPORT-007`, workspace complet et deux smokes Devnet passés avant publication stable.

View File

@@ -0,0 +1,474 @@
<!-- file: docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md -->
<!-- version: 2 -->
# Validation finale `0.2.4` — HTTP Blocks + Economics + compliance finale
## Objet
Cette matrice porte la preuve de clôture stable de `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`.
Elle complète les validations historiques :
- [`003-V0_2_1_ONCHAIN_HTTP.md`](003-V0_2_1_ONCHAIN_HTTP.md) — foundation HTTP et registre initial ;
- [`004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) — 22 wrappers Accounts/Tokens/Cluster ;
- [`005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) — réaudit `KSP-TRANSPORT-007` des 37 wrappers acquis avant `0.2.4` ;
- [`006-V0_2_3_HTTP_TRANSACTIONS.md`](006-V0_2_3_HTTP_TRANSACTIONS.md) — 11 wrappers Transactions et clôture stable `0.2.3`.
`0.2.4-pre.009` n'ajoute pas de nouvelle méthode RPC métier. Il agrège les preuves de complétude, étend le smoke live aux familles nouvellement livrées, synchronise la documentation et prépare `0.2.5`.
`0.2.4-rel.001` est strictement publicationnelle : aucun contrat RPC ou comportement réseau nest ajouté après la candidate.
## Base opérateur validée
La base `0.2.4-pre.008-fix.001` a été validée le **18 août 2026** avec :
```text
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --workspace --all-targets OK
Transport
240 unit passed
26 public API passed
21 release completeness passed
1 smoke live ignored par défaut
0 failure
```
Le fix ne modifie pas le contrat `getInflationReward`; il ajoute uniquement le `return` explicite exigé par la policy Clippy workspace.
## Réaudit officiel final — inventaire HTTP
Dernier contrôle effectué le **18 août 2026** contre l'index HTTP Solana courant :
```text
https://solana.com/docs/rpc/http
```
L'index contient toujours exactement **52 méthodes HTTP courantes**.
La navigation Deprecated officielle, visible depuis les pages sous :
```text
https://solana.com/docs/rpc/deprecated/
```
contient toujours exactement **14 méthodes historiques**.
Le rapprochement a été réalisé sur les **noms exacts**, pas uniquement sur les compteurs :
```text
official current names == KSP current registry names == 52/52
official deprecated names == KSP historical registry names == 14/14
missing == 0
extra == 0
```
Aucun nouvel endpoint HTTP issu de SIMD-0180 ou d'une autre évolution d'interface n'apparaît dans l'index officiel actuel.
## Baseline Agave et audit SIMD final
La baseline runtime conservée pour le contrat de cette release reste **Agave `v4.2.1`**, déjà auditée depuis `pre.001`.
Le calendrier public Agave v4.2 observé le 18 août 2026 montre que le rollout v4.2 est encore en cours : Devnet a été livré en juillet/août et les étapes Mainnet-beta d'adoption générale/activation sont positionnées en août. Cette situation ne justifie pas de remplacer rétroactivement la baseline wire de la release sans différence stable démontrée.
Audit final minimal imposé par le plan :
| SIMD | Statut observé le 2026-08-18 | Conséquence HTTP KSP |
|---------------------------------------------------|------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `0180` Vote Account Address Keyed Leader Schedule | Review | aucune nouvelle méthode HTTP officielle ; ne pas changer `getLeaderSchedule` par anticipation |
| `0298` Bank Hash in Block Footer | Idea | aucun `bankHash` ajouté au wire stable `getBlock` |
| `0301` parent bank hash | PR fermé, non mergé | aucun `parentBankHash` spéculatif |
| `0307` Add Block Footer | Review | aucun `footer` ajouté à `SolanaGetBlockConfig`/`SolanaConfirmedBlock` tant que la baseline stable ne l'expose pas |
| `0385` Transaction V1 | Review | conserver `SolanaTransactionVersion::Number(u8)` générique et la canary version `1` |
| `0490` Stake v5 | Review | `getStakeMinimumDelegation` reflète uniquement la valeur runtime ; aucun minimum local codé en dur |
| `0550` Double Disinflation Rate | Review | `getInflationGovernor`/`getInflationRate` reflètent les valeurs runtime ; aucune formule locale |
| `0553` Base Inclusion and Resource-based Fee | Draft | changement économique/consensus à surveiller ; aucun nouveau wrapper ou champ HTTP stable ajouté |
Extensions wire déjà acquises et réauditées explicitement : **SIMD-0118** et **SIMD-0291**.
| SIMD | Statut observé le 2026-08-18 | Contrat déjà matérialisé dans KSP |
|-----------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `0118` Partitioned Epoch Rewards Distribution | Activated | `getBlock.numRewardPartitions` reste lossless : omission, `null` et valeur sont distincts ; KSP ne synthétise jamais `0` |
| `0291` Commission Rate in Basis Points | Review | `commission` et `commissionBps` restent indépendants dans les rewards de bloc et d'inflation ; les sous-arbres transaction restent lossless |
Le statut documentaire `Review` de SIMD-0291 ne justifie pas de supprimer un champ déjà livré par Agave `v4.2.1`. Inversement, KSP ne déduit jamais `commissionBps` depuis `commission`, ni l'inverse.
Sources SIMD :
```text
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0118-partitioned-epoch-reward-distribution.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0180-vote-account-leader-schedule.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0291-commission-rate-in-basis-points.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0298-bank-hash-in-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/pull/301
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0307-add-block-footer.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0490-upgrade-stake-to-v5.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0550-double-disinflation.md
https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0553-resource-fee-burn.md
```
## Compliance des 52 méthodes courantes
La canary `public_v0_2_4_pre_009_all_52_current_typed_wrappers_and_legacy_forms_are_available_from_crate_root` référence directement chaque méthode Rust publique ci-dessous. Une méthode ne compte donc pas comme typed-complete uniquement parce qu'un descriptor ou l'exécuteur raw existe.
| Méthode RPC | Catégorie | Release typed | Classe | Wrapper public |
|-------------------------------------|--------------|---------------|--------------------------------------|------------------------------------------|
| `getAccountInfo` | Accounts | `0.2.2` | Read / RetrySafe | `get_account_info` |
| `getBalance` | Accounts | `0.2.1` | Read / RetrySafe | `get_balance` |
| `getLargestAccounts` | Accounts | `0.2.2` | Read / RetrySafe | `get_largest_accounts` |
| `getMinimumBalanceForRentExemption` | Accounts | `0.2.2` | Read / RetrySafe | `get_minimum_balance_for_rent_exemption` |
| `getMultipleAccounts` | Accounts | `0.2.2` | Read / RetrySafe | `get_multiple_accounts` |
| `getProgramAccounts` | Accounts | `0.2.2` | Read / RetrySafe | `get_program_accounts` |
| `getTokenAccountBalance` | Tokens | `0.2.2` | Read / RetrySafe | `get_token_account_balance` |
| `getTokenAccountsByDelegate` | Tokens | `0.2.2` | Read / RetrySafe | `get_token_accounts_by_delegate` |
| `getTokenAccountsByOwner` | Tokens | `0.2.2` | Read / RetrySafe | `get_token_accounts_by_owner` |
| `getTokenLargestAccounts` | Tokens | `0.2.2` | Read / RetrySafe | `get_token_largest_accounts` |
| `getTokenSupply` | Tokens | `0.2.2` | Read / RetrySafe | `get_token_supply` |
| `getFeeForMessage` | Transactions | `0.2.3` | Read / RetrySafe | `get_fee_for_message` |
| `getLatestBlockhash` | Transactions | `0.2.3` | Read / RetrySafe | `get_latest_blockhash` |
| `getRecentPrioritizationFees` | Transactions | `0.2.3` | Read / RetrySafe | `get_recent_prioritization_fees` |
| `getSignaturesForAddress` | Transactions | `0.2.3` | Read / RetrySafe | `get_signatures_for_address` |
| `getSignatureStatuses` | Transactions | `0.2.3` | Read / RetrySafe | `get_signature_statuses` |
| `getTransaction` | Transactions | `0.2.3` | Read / RetrySafe | `get_transaction` + legacy |
| `getTransactionCount` | Transactions | `0.2.3` | Read / RetrySafe | `get_transaction_count` |
| `isBlockhashValid` | Transactions | `0.2.3` | Read / RetrySafe | `is_blockhash_valid` |
| `requestAirdrop` | Transactions | `0.2.3` | WriteSubmission / NeverAfterDispatch | `request_airdrop` |
| `sendTransaction` | Transactions | `0.2.3` | WriteSubmission / NeverAfterDispatch | `send_transaction` |
| `simulateTransaction` | Transactions | `0.2.3` | Simulation / RetrySafe | `simulate_transaction` |
| `getBlock` | Blocks | `0.2.4` | Read / RetrySafe | `get_block` + legacy |
| `getBlockCommitment` | Blocks | `0.2.4` | Read / RetrySafe | `get_block_commitment` |
| `getBlockHeight` | Blocks | `0.2.4` | Read / RetrySafe | `get_block_height` |
| `getBlockProduction` | Blocks | `0.2.4` | Read / RetrySafe | `get_block_production` |
| `getBlocks` | Blocks | `0.2.4` | Read / RetrySafe | `get_blocks` |
| `getBlocksWithLimit` | Blocks | `0.2.4` | Read / RetrySafe | `get_blocks_with_limit` |
| `getBlockTime` | Blocks | `0.2.4` | Read / RetrySafe | `get_block_time` |
| `getFirstAvailableBlock` | Blocks | `0.2.4` | Read / RetrySafe | `get_first_available_block` |
| `getRecentPerformanceSamples` | Blocks | `0.2.4` | Read / RetrySafe | `get_recent_performance_samples` |
| `minimumLedgerSlot` | Blocks | `0.2.4` | Read / RetrySafe | `minimum_ledger_slot` |
| `getClusterNodes` | Cluster | `0.2.2` | Read / RetrySafe | `get_cluster_nodes` |
| `getEpochInfo` | Cluster | `0.2.2` | Read / RetrySafe | `get_epoch_info` |
| `getEpochSchedule` | Cluster | `0.2.2` | Read / RetrySafe | `get_epoch_schedule` |
| `getGenesisHash` | Cluster | `0.2.1` | Read / RetrySafe | `get_genesis_hash` |
| `getHealth` | Cluster | `0.2.1` | Read / RetrySafe | `get_health` |
| `getHighestSnapshotSlot` | Cluster | `0.2.2` | Read / RetrySafe | `get_highest_snapshot_slot` |
| `getIdentity` | Cluster | `0.2.2` | Read / RetrySafe | `get_identity` |
| `getLeaderSchedule` | Cluster | `0.2.2` | Read / RetrySafe | `get_leader_schedule` |
| `getMaxRetransmitSlot` | Cluster | `0.2.2` | Read / RetrySafe | `get_max_retransmit_slot` |
| `getMaxShredInsertSlot` | Cluster | `0.2.2` | Read / RetrySafe | `get_max_shred_insert_slot` |
| `getSlot` | Cluster | `0.2.2` | Read / RetrySafe | `get_slot` |
| `getSlotLeader` | Cluster | `0.2.2` | Read / RetrySafe | `get_slot_leader` |
| `getSlotLeaders` | Cluster | `0.2.2` | Read / RetrySafe | `get_slot_leaders` |
| `getVersion` | Cluster | `0.2.1` | Read / RetrySafe | `get_version` |
| `getVoteAccounts` | Cluster | `0.2.2` | Read / RetrySafe | `get_vote_accounts` |
| `getInflationGovernor` | Economics | `0.2.4` | Read / RetrySafe | `get_inflation_governor` |
| `getInflationRate` | Economics | `0.2.4` | Read / RetrySafe | `get_inflation_rate` |
| `getInflationReward` | Economics | `0.2.4` | Read / RetrySafe | `get_inflation_reward` |
| `getStakeMinimumDelegation` | Economics | `0.2.4` | Read / RetrySafe | `get_stake_minimum_delegation` |
| `getSupply` | Economics | `0.2.4` | Read / RetrySafe | `get_supply` |
Les deux formes legacy encore supportées sont couvertes séparément et marquées deprecated :
```text
getTransaction -> HttpTransportPool::get_transaction_legacy
getBlock -> HttpTransportPool::get_block_legacy
```
Partition exacte :
```text
0.2.1 foundation 4
0.2.2 Accounts/Tokens/Cluster 22
0.2.3 Transactions 11
0.2.4 Blocks/Economics 15
--------------------------------
total 52
```
Classification globale attendue :
```text
Read / RetrySafe
Simulation / RetrySafe
WriteSubmission / NeverAfterDispatch
```
Les seules write submissions actuelles restent `requestAirdrop` et `sendTransaction`; aucune classe `NeverAfterDispatch` n'est affaiblie par `0.2.4`.
## Compliance des 14 historiques
Les 14 entrées restent découvrables pour audit/compliance mais sont `Deprecated / Removed / NotApplicable` et ne sont pas envoyées au réseau :
| Méthode historique | Documentation | Runtime KSP | Remplacement/aide |
|-------------------------------------|---------------|-------------|-------------------------------------|
| `confirmTransaction` | Deprecated | Removed | getSignatureStatuses |
| `getConfirmedBlock` | Deprecated | Removed | getBlock |
| `getConfirmedBlocks` | Deprecated | Removed | getBlocks |
| `getConfirmedBlocksWithLimit` | Deprecated | Removed | getBlocksWithLimit |
| `getConfirmedSignaturesForAddress2` | Deprecated | Removed | getSignaturesForAddress |
| `getConfirmedTransaction` | Deprecated | Removed | getTransaction |
| `getFeeCalculatorForBlockhash` | Deprecated | Removed | isBlockhashValid / getFeeForMessage |
| `getFeeRateGovernor` | Deprecated | Removed | getFeeForMessage |
| `getFees` | Deprecated | Removed | getFeeForMessage |
| `getRecentBlockhash` | Deprecated | Removed | getLatestBlockhash |
| `getSignatureConfirmation` | Deprecated | Removed | getSignatureStatuses |
| `getSignatureStatus` | Deprecated | Removed | getSignatureStatuses |
| `getSnapshotSlot` | Deprecated | Removed | getHighestSnapshotSlot |
| `getStakeActivation` | Deprecated | Removed | aucun remplacement direct |
La canary `release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact` fige simultanément :
```text
52 noms current exacts
14 noms historiques exacts
4 / 22 / 11 / 15 par HttpRpcCoverageRelease
Supported pour les 52 current
Deprecated + Removed + NotApplicable pour les 14 historiques
RetrySafe pour Read/Simulation current
NeverAfterDispatch pour WriteSubmission current
```
## `KSP-TRANSPORT-007` — preuve globale 52/52
Le réaudit `0.2.3` a déjà conclu **37/37 conformes** avant l'ouverture de `0.2.4`.
Les 15 nouveaux wrappers appliquent la même règle sans réduction de contrat.
### Blocks — 10/10
Points de sensibilité couverts :
- `getBlock` moderne + bare encoding legacy deprecated ;
- encodings `binary/base58/base64/json/jsonParsed` réellement supportés par la baseline ;
- `transactionDetails = full/signatures/none/accounts` ;
- `maxSupportedTransactionVersion` et version numérique générique, y compris canary `1` ;
- distinction résultat `null` / erreurs RPC ;
- `transactions`, `signatures`, `rewards`, `numRewardPartitions` avec préservation des états wire pertinents ;
- `commission` nullable + `commissionBps` indépendant ;
- `getBlocks` avec ses quatre overloads exacts et `RpcContextConfig` complet ;
- `getBlocksWithLimit(0)` valide et plafond 500000 ;
- `getRecentPerformanceSamples <= 720` et ancien wire sans `numNonVoteTransactions` ;
- `getBlockProduction` identity/range/context sans restriction de commitment inventée ;
- wrappers sans config exactement en `params: []`.
### Economics — 5/5
Points de sensibilité couverts :
- aucune formule d'inflation locale ;
- aucun minimum de délégation codé en dur ;
- `getSupply` conserve `excludeNonCirculatingAccountsList` omis/false/true ;
- `getInflationReward` conserve ordre, doublons, `null` positionnels et cardinalité ;
- aucun plafond d'adresses inventé pour `getInflationReward` ;
- `epoch`, `commitment`, `minContextSlot` transmis ;
- `getInflationReward` rejette `processed` avant I/O car la voie runtime exige au moins `confirmed` ;
- `commission` nullable et `commissionBps` présent/omis/null indépendants ;
- erreurs RPC de contexte/rewards period conservées comme erreurs applicatives distantes.
Verdict final :
```text
KSP-TRANSPORT-007 current wrappers == 52/52
remédiation fonctionnelle finale == aucune
```
## Smoke Devnet Transport pur
Le smoke Transport reste opt-in, programmatique et read-only :
```text
settings programmatiques
-> getAccountInfo
-> getTokenAccountsByOwner
-> getEpochInfo
-> getVoteAccounts
-> getLatestBlockhash
-> isBlockhashValid
-> getTransactionCount
-> getBlockHeight
-> getInflationRate
-> getStakeMinimumDelegation
```
Les trois derniers appels ajoutent une traversée live représentative des familles Blocks/Economics sans dépendre d'un slot historique particulier, d'un compte reward spécifique ou d'une valeur économique codée en dur.
Commande :
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
```
Le smoke historique de composition reste séparé et transitoire :
```bash
cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapture
```
Il ne devient pas un modèle de placement des futurs smokes cross-crates.
## Frontières et graphes Cargo
Frontière attendue :
```text
ksp-config-lib -> ksp-onchain-transport-lib
ksp-onchain-transport-lib -> ksp-core-lib
ksp-onchain-transport-lib -> ksp-logging-lib
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
```
`0.2.4` n'ajoute aucune dépendance externe au `Cargo.toml` de Transport.
Graphes à inspecter sur la candidate :
```bash
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib -d
cargo tree -p ksp-onchain-transport-lib -e features
cargo tree -p ksp-onchain-transport-lib -e normal
cargo tree -p ksp-config-lib
cargo tree -p ksp-config-lib -d
cargo tree -p ksp-config-lib -e features
cargo tree -p ksp-config-lib -e normal
```
Interprétation importante : `tracing` peut apparaître **transitivement** sous `ksp-logging-lib`, ce qui est conforme à l'architecture. L'interdit porte sur une dépendance **directe** de `ksp-onchain-transport-lib` vers `tracing`. Les graphes doivent donc être lus par chaîne de dépendances et non validés par la simple présence/absence du nom `tracing` dans l'arbre complet.
Les canaries workspace de dépendances restent des règles globales et ne sont pas déplacées dans Transport.
## Gate opérateur finale `0.2.4-pre.009`
Les résultats opérateur fournis le **18 août 2026** confirment les tests de clôture suivants :
```text
cargo test -p ksp-config-lib
95 unit passed
5 ownership passed
13 public API passed
1 smoke Devnet ignored par défaut
cargo test -p ksp-core-lib
14 unit passed
3 public API passed
2 workspace_dependencies passed
1 workspace_logging passed
cargo test -p ksp-app-config-desk
63 unit passed
2 desktop_contract passed
2 desktop_security passed
1 public API passed
cargo test --workspace passed
Transport observé dans le workspace
240 unit passed
27 public API passed
22 release completeness passed
1 smoke Devnet ignored par défaut
```
Le workspace valide également les suites Logging, dont les canaries d'ownership de la façade tracing. Les tests `workspace_dependencies` de Core confirment notamment que le manifest Transport préserve le firewall de dépendances.
Les deux smokes réseau ont ensuite été exécutés explicitement :
```text
Transport pur :
programmatic_devnet_transport_reaches_accounts_tokens_cluster_transactions_blocks_and_economics_reads
-> 1 passed
Config -> Transport :
committed_devnet_transport_profile_reaches_all_foundation_canaries
-> 1 passed
```
Le premier smoke est durable et appartient à Transport. Le second reste l'exception transitoire cross-crates déjà documentée.
### Graphes Cargo
La candidate définit les huit graphes et recherches de frontière à inspecter dans la section précédente. Le journal opérateur fourni pour `rel.001` ne contient pas leurs sorties textuelles ; cette matrice **ne les déclare donc pas comme exécutés**.
En revanche, les canaries automatisées correspondantes passent dans le workspace :
```text
workspace_dependency_table_does_not_activate_consumer_features passed
transport_manifest_preserves_ksp_dependency_firewall passed
behavioral_crates_own_explicit_tracing_targets passed
workspace_crates_do_not_bypass_ksp_logging_facade passed
```
Les contrôles `cargo tree` restent à rejouer avant création du tag stable afin de disposer d'une dernière preuve manuelle de graphe sur la version `0.2.4` publiée.
## Documentation et prochaine release
La publication synchronise :
```text
CHANGELOG.md
ROADMAP.md
README/USAGE Transport
inventaire composant
séquence fonctionnelle
plan 011
index docs/plans/validation/prompts
```
Le prompt suivant est :
```text
prompts/010-V0_2_5_START_PROMPT.md
0.2.5 — Wallet foundation
```
Sa **version 2**, livrée par le fix documentaire `0.2.4-pre.009-fix.001`, ajoute notamment :
```text
interopérabilité externe du .kspwallet
threat model de bruteforce offline
VIEW / OWNER indépendants
Pubkey / alias / notes non lisibles wallet verrouillé
key slots / key wrapping à auditer
rotation des passwords sans changement de keypair
read-only cryptographique VIEW à étudier
```
## Publication `rel.001`
La livraison publicationnelle applique :
```text
workspace.package.version -> 0.2.4
ROADMAP : 0.2.4 -> [X]
CHANGELOG : synthèse stable 0.2.4
plan 011 / validation 007 : statut stable + preuves opérateur
deltas/0.2.4/rel.001.md
commit attendu : v0.2.4-rel.001
tag stable après gate finale : v0.2.4
```
Aucun wrapper, DTO, comportement runtime, dependency ou feature Cargo n'est ajouté pendant `rel.001`.
Gate finale post-application avant commit/tag :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib -d
cargo tree -p ksp-onchain-transport-lib -e features
cargo tree -p ksp-onchain-transport-lib -e normal
cargo tree -p ksp-config-lib
cargo tree -p ksp-config-lib -d
cargo tree -p ksp-config-lib -e features
cargo tree -p ksp-config-lib -e normal
```
Les smokes Devnet ont déjà été passés sur `pre.009`; comme `rel.001` ne modifie aucun code/runtime, leur répétition n'est pas nécessaire à la démonstration fonctionnelle, mais reste autorisée avant tag si l'opérateur souhaite une ultime vérification réseau.

View File

@@ -1,5 +1,5 @@
<!-- file: prompts/000-README.md --> <!-- file: prompts/000-README.md -->
<!-- version: 13 --> <!-- version: 15 -->
# Prompts KSP # Prompts KSP
@@ -30,3 +30,4 @@ Le prompt générique `0.1.x` a été affiné pendant `0.0.3` puis remplacé par
- [`007-V0_2_2_START_PROMPT.md`](007-V0_2_2_START_PROMPT.md) — prompt préparé par la dernière prerelease de `0.2.1`, destiné à ouvrir `0.2.2 — HTTP Accounts + Tokens + Cluster` après publication stable de `0.2.1`; il cible les 22 wrappers typés restants de ces familles et impose un nouvel audit officiel/gate de sizing à `pre.001`. - [`007-V0_2_2_START_PROMPT.md`](007-V0_2_2_START_PROMPT.md) — prompt préparé par la dernière prerelease de `0.2.1`, destiné à ouvrir `0.2.2 — HTTP Accounts + Tokens + Cluster` après publication stable de `0.2.1`; il cible les 22 wrappers typés restants de ces familles et impose un nouvel audit officiel/gate de sizing à `pre.001`.
- [`008-V0_2_3_START_PROMPT.md`](008-V0_2_3_START_PROMPT.md) — prompt préparé par la dernière prerelease de `0.2.2`, destiné à ouvrir `0.2.3 — HTTP Transactions` après publication stable de `0.2.2`; il cible les 11 méthodes Transactions et impose un audit actuel ainsi que la politique no-resend des write submissions. - [`008-V0_2_3_START_PROMPT.md`](008-V0_2_3_START_PROMPT.md) — prompt préparé par la dernière prerelease de `0.2.2`, destiné à ouvrir `0.2.3 — HTTP Transactions` après publication stable de `0.2.2`; il cible les 11 méthodes Transactions et impose un audit actuel ainsi que la politique no-resend des write submissions.
- [`009-V0_2_4_START_PROMPT.md`](009-V0_2_4_START_PROMPT.md) — prompt préparé par `0.2.3-pre.009`, destiné à ouvrir `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale` après publication stable de `0.2.3`; il cible les 15 wrappers restants et impose `KSP-TRANSPORT-007` ainsi qu'un nouvel audit/sizing à `pre.001`. - [`009-V0_2_4_START_PROMPT.md`](009-V0_2_4_START_PROMPT.md) — prompt préparé par `0.2.3-pre.009`, destiné à ouvrir `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale` après publication stable de `0.2.3`; il cible les 15 wrappers restants et impose `KSP-TRANSPORT-007` ainsi qu'un nouvel audit/sizing à `pre.001`.
- [`010-V0_2_5_START_PROMPT.md`](010-V0_2_5_START_PROMPT.md) — prompt préparé par `0.2.4-pre.009` puis finalisé en version 2 par `pre.009-fix.001`, destiné à ouvrir `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`; il impose audit/threat-model/sizing avant choix cryptographiques et cadre `.kspwallet` interopérable, capacités indépendantes VIEW/OWNER, metadata protégées, key slots/rotations, signature, persistence atomique et import/export extensible sans `WalletPolicy`.

File diff suppressed because it is too large Load Diff